diff --git a/xcmd/commander.go b/xcmd/commander.go index ac3502a..7741af3 100644 --- a/xcmd/commander.go +++ b/xcmd/commander.go @@ -8,10 +8,23 @@ import ( "io" "os" "strings" -) -type MiddlewareFunc = func(ctx context.Context, c *Command, ff *flag.FlagSet, args []string, next Executer) error + "github.com/sandwich-go/xconf/xflag" +) +// MiddlewareFunc 中间件方法 +// cmd *Command : 为当前执行的命令对象 +// ff *flag.FlagSet: 为当前命令对象解析中使用的FlagSet,如为pre中间件则未解析,否则为解析过后的FlagSet +// args []string : 为当前命令行参数,也就是FlagSet要去解析的参数列表 +// next : 下一步要执行的方法,可能是下一个中间件或者目标Executer方法 +type MiddlewareFunc = func(ctx context.Context, cmd *Command, ff *flag.FlagSet, args []string, next Executer) error + +// Command 表征一条命令或一个命令Group +// 中间件分preMiddleware与middleware +// 执行顺序为:preMiddleware -> Parser -> middleware -> Executer +// - preMiddleware执行的时候并没有进行任何的FlagSet解析,可以在此时进行一些自定义的Flag创建 +// - Parser为Option传入的中间件,一般无需自行实现,Parser主要是将配置文件、FlagSet、Env等遵循XConf规则解析到Option传入的Bind对象上,如传nil,则会调用FlagSet的Parse方法 +// - middleware执行的时候已经完成了参数对象的绑定解析 type Command struct { name string cc ConfigInterface @@ -20,12 +33,15 @@ type Command struct { middleware []MiddlewareFunc preMiddleware []MiddlewareFunc usageNamePath []string + parent *Command // 目前只用于确认命令是否已经有父节点 } +// NewCommand 创建一条命令 func NewCommand(name string, opts ...ConfigOption) *Command { return NewCommandWithConfig(name, NewConfig(opts...)) } +// NewCommandWithConfig 创建一条命令 func NewCommandWithConfig(name string, cc ConfigInterface) *Command { c := &Command{ name: name, @@ -36,22 +52,34 @@ func NewCommandWithConfig(name string, cc ConfigInterface) *Command { return c } -func (c *Command) Config() ConfigInterface { return c.cc } - +// Use 添加中间件,在此之后添加的子命令都会继承该中间件 +// 执行顺序为:preMiddleware -> Parser -> middleware -> Executer func (c *Command) Use(middleware ...MiddlewareFunc) *Command { c.middleware = append(c.middleware, middleware...) return c } +// UsePre 添加preMiddleware中间件,pre中间件运行在Parser之前 +// 执行顺序为:preMiddleware -> Parser -> middleware -> Executer func (c *Command) UsePre(preMiddleware ...MiddlewareFunc) *Command { c.preMiddleware = append(c.preMiddleware, preMiddleware...) return c } +// AddCommand 添加一条子命令,可以携带中间件信息,等同于Add(xxxxx).Use或者AddCommand(xxxx).Use func (c *Command) AddCommand(sub *Command, middleware ...MiddlewareFunc) { + if sub.parent != nil { + panic(fmt.Sprintf("command:%s has parent:%s", sub.name, sub.parent.name)) + } sub.usageNamePath = append(c.usageNamePath, sub.usageNamePath...) sub.middleware = combineMiddlewareFunc(c.middleware, middleware...) sub.preMiddleware = combineMiddlewareFunc(c.preMiddleware, sub.preMiddleware...) + sub.parent = c + // 如果该命令在添加子命令前没有父节点,则需要将父节点的中间件追加上 + for _, v := range sub.commands { + v.middleware = combineMiddlewareFunc(c.middleware, v.middleware...) + v.preMiddleware = combineMiddlewareFunc(c.preMiddleware, v.preMiddleware...) + } c.commands = append(c.commands, sub) } @@ -62,6 +90,10 @@ func combineMiddlewareFunc(middlewareNow []MiddlewareFunc, middleware ...Middlew return m } +// Config 获取配置,允许运行期调整,但只在Parser运行前生效 +func (c *Command) Config() ConfigInterface { return c.cc } + +// Add 添加一条子命令 func (c *Command) Add(name string, opts ...ConfigOption) *Command { sub := NewCommand(name, opts...) c.AddCommand(sub) @@ -75,24 +107,43 @@ func (c *Command) wrapErr(err error) error { return fmt.Errorf("command: %s err:%s", strings.Join(c.usageNamePath, " "), err.Error()) } +// Execute 执行参数解析驱动命令执行 func (c *Command) Execute(ctx context.Context, args ...string) error { if len(args) != 0 { // 尝试在当前命令集下寻找子命令 subCommandName := args[0] for _, cmd := range c.commands { - if cmd.GetName() != subCommandName { + if cmd.Name() != subCommandName { continue } return cmd.Execute(ctx, args[1:]...) } } ff := flag.NewFlagSet(strings.Join(c.usageNamePath, "/"), flag.ContinueOnError) + // 默认 usage 无参 + ff.Usage = func() { + c.Explain(c.Output) + xflag.PrintDefaults(ff) + } + var allMiddlewares []MiddlewareFunc allMiddlewares = append(allMiddlewares, c.preMiddleware...) - allMiddlewares = append(allMiddlewares, c.cc.GetParser()) + allMiddlewares = append(allMiddlewares, parser) allMiddlewares = append(allMiddlewares, c.middleware...) return ChainMiddleware(allMiddlewares...)(ctx, c, ff, args, exec) } + +func parser(ctx context.Context, c *Command, ff *flag.FlagSet, args []string, next Executer) error { + if c.cc.GetBind() == nil { + err := ff.Parse(args) + if err != nil { + return err + } + return next(ctx, c, ff, args) + } + return c.cc.GetParser()(ctx, c, ff, args, next) +} + func exec(ctx context.Context, c *Command, ff *flag.FlagSet, args []string) error { executer := c.Config().GetExecute() if executer == nil { @@ -101,9 +152,19 @@ func exec(ctx context.Context, c *Command, ff *flag.FlagSet, args []string) erro return executer(context.Background(), c, ff, args) } -func (c *Command) GetName() string { return c.name } -func (c *Command) Usage() string { return c.cc.GetSynopsis() } +// Name 获取当前命令名称 +func (c *Command) Name() string { return c.name } + +// NamePath 获取当前命令路径 +func (c *Command) NamePath() []string { return c.usageNamePath } + +// Usage 获取当前命令路径 +func (c *Command) Usage() string { return c.cc.GetUsage() } + +// Short 获取当前命令Short Usage +func (c *Command) Short() string { return c.cc.GetShort() } +// SubCommand 由当前命令扩展子命令, 继承Bing,BindPath,XConfOption等参数 func (c *Command) SubCommand(name string, opts ...ConfigOption) *Command { cc := NewConfig( WithBind(c.cc.GetBind()), @@ -116,6 +177,7 @@ func (c *Command) SubCommand(name string, opts ...ConfigOption) *Command { return sub } +// Check 检查当前命令及子命令是否有路径绑定错误等信息 func (c *Command) Check() error { for _, v := range c.commands { binder := c.cc.GetParser() diff --git a/xcmd/default.go b/xcmd/default.go index 9dd448c..4b04152 100644 --- a/xcmd/default.go +++ b/xcmd/default.go @@ -13,17 +13,39 @@ var rootCmd = NewCommand(path.Base(os.Args[0]), WithExecute(func(ctx context.Con return nil })) -func Use(middleware ...MiddlewareFunc) *Command { return rootCmd.Use(middleware...) } -func UsePre(middleware ...MiddlewareFunc) *Command { return rootCmd.UsePre(middleware...) } +// Use 添加中间件,在此之后添加的子命令都会继承该中间件 +// 执行顺序为:preMiddleware -> Parser -> middleware -> Executer +func Use(middleware ...MiddlewareFunc) *Command { return rootCmd.Use(middleware...) } + +// UsePre 添加preMiddleware中间件,pre中间件运行在Parser之前 +// 执行顺序为:preMiddleware -> Parser -> middleware -> Executer +func UsePre(middleware ...MiddlewareFunc) *Command { return rootCmd.UsePre(middleware...) } + +// Add 添加一条子命令 +func Add(name string, opts ...ConfigOption) *Command { return rootCmd.Add(name, opts...) } + +// AddCommand 添加一条子命令,可以携带中间件信息,等同于Add(xxxxx).Use或者AddCommand(xxxx).Use func AddCommand(sub *Command, middleware ...MiddlewareFunc) { rootCmd.AddCommand(sub, middleware...) } -func Config() ConfigInterface { return rootCmd.cc } -func Execute(ctx context.Context, args ...string) error { return rootCmd.Execute(ctx, args...) } -func Explain(w io.Writer) { rootCmd.Explain(w) } -func Check() error { return rootCmd.Check() } +// Config 获取配置,允许运行期调整,但只在Parser运行前生效 +func Config() ConfigInterface { return rootCmd.Config() } + +// Execute 执行参数解析驱动命令执行 +func Execute(ctx context.Context, args ...string) error { return rootCmd.Execute(ctx, args...) } + +// Explain 打印使用说明 +func Explain(w io.Writer) { rootCmd.Explain(w) } + +// Check 检查当前命令及子命令是否有路径绑定错误等信息 +func Check() error { return rootCmd.Check() } + +// SubCommand 由当前命令扩展子命令, 继承Bing,BindPath,XConfOption等参数 func SubCommand(name string, opts ...ConfigOption) *Command { return rootCmd.SubCommand(name, opts...) } +// SetRootCommand 设定当前默认的根命令 func SetRootCommand(root *Command) { rootCmd = root } -func RootCommand() *Command { return rootCmd } + +// RootCommand 获取根命令 +func RootCommand() *Command { return rootCmd } diff --git a/xcmd/error.go b/xcmd/error.go index 04bcb87..4513cf7 100644 --- a/xcmd/error.go +++ b/xcmd/error.go @@ -1,13 +1,15 @@ package xcmd import ( - "errors" "flag" "strings" ) -var ErrNoNeedBind = errors.New("no need bind") +// ErrHelp is the error returned if the -help or -h flag is invoked +// but no such flag is defined. +var ErrHelp = flag.ErrHelp +// IsErrHelp 检查错误是否是ErrHelp func IsErrHelp(err error) bool { if err == nil { return false diff --git a/xcmd/explain.go b/xcmd/explain.go index 8ea88b6..65c0aa8 100644 --- a/xcmd/explain.go +++ b/xcmd/explain.go @@ -10,9 +10,10 @@ import ( type byGroupName []*Command func (p byGroupName) Len() int { return len(p) } -func (p byGroupName) Less(i, j int) bool { return p[i].GetName() < p[j].GetName() } +func (p byGroupName) Less(i, j int) bool { return p[i].Name() < p[j].Name() } func (p byGroupName) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +// Explain 打印使用说明 func (c *Command) Explain(w io.Writer) { explainGroup(w, c) } // explainGroup explains all the subcommands for a particular group. @@ -27,7 +28,7 @@ func explainGroup(w io.Writer, c *Command) { fmt.Fprintf(w, "Available Commands:\n\n") sort.Sort(byGroupName(c.commands)) var level = []bool{} - lines := PrintCommand(c, level) + lines := printCommand(c, level) // lines = xutil.TableFormatLines(lines, magic) fmt.Fprintln(w, strings.Join(lines, "\n")) fmt.Fprintf(w, "\n") @@ -68,14 +69,14 @@ func applyPadding(filler string) string { const magic = "\x00" -func PrintCommand(c *Command, lvl []bool) (lines []string) { - lines = append(lines, fmt.Sprintf("%s%s(%d,%d) %s %s", getPrefix(lvl), c.name, len(c.preMiddleware), len(c.middleware), magic, c.cc.GetSynopsis())) +func printCommand(c *Command, lvl []bool) (lines []string) { + lines = append(lines, fmt.Sprintf("%s%s(%d,%d) %s %s", getPrefix(lvl), c.name, len(c.preMiddleware), len(c.middleware), magic, c.cc.GetShort())) var level = append(lvl, false) for i := 0; i < len(c.commands); i++ { if i+1 == len(c.commands) { level[len(level)-1] = true } - subLines := PrintCommand(c.commands[i], level) + subLines := printCommand(c.commands[i], level) lines = append(lines, subLines...) } return lines diff --git a/xcmd/field2flagset.go b/xcmd/field2flagset.go index c9eb779..6c764c6 100644 --- a/xcmd/field2flagset.go +++ b/xcmd/field2flagset.go @@ -8,6 +8,18 @@ import ( ) // GenFieldPathStruct 生成filedPath struct +// todo 应随optiongen生成,手动指定FieldPath的时候可以防止出错,目前需要手动定义利用Command.Check检查 +// type ConfigFieldPath struct { +// HttpAddress string +// Timeouts string +// } + +// func NewConfigFieldPath() *ConfigFieldPath { +// return &ConfigFieldPath{ +// HttpAddress: "http_address", +// Timeouts: "timeouts", +// } +// } func GenFieldPathStruct(name string, fields map[string]xconf.StructFieldPathInfo) string { var lines []string structName := strings.Title(name) + "FieldPath" diff --git a/xcmd/gen_config_optiongen.go b/xcmd/gen_config_optiongen.go index 0a1898e..8398740 100644 --- a/xcmd/gen_config_optiongen.go +++ b/xcmd/gen_config_optiongen.go @@ -5,7 +5,6 @@ package xcmd import ( "context" - "errors" "flag" "github.com/sandwich-go/xconf" @@ -17,8 +16,8 @@ type config struct { Bind interface{} // annotation@BindFieldPath(comment="命令绑定的参数FieldPath,如空则全部绑定") BindFieldPath []string - // annotation@Synopsis(comment="少于一行的操作说明") - Synopsis string + // annotation@Short(comment="少于一行的操作说明") + Short string // annotation@Usage(comment="详细说明") Usage string // annotation@Execute(comment="执行方法") @@ -27,7 +26,7 @@ type config struct { XConfOption []xconf.Option // annotation@Parser(comment="配置解析") Parser MiddlewareFunc - // annotation@Executer(comment="配置解析") + // annotation@Executer(comment="当未配置Parser时触发该默认逻辑") OnExecuterLost Executer } @@ -85,12 +84,12 @@ func WithBindFieldPathAppend(v ...string) ConfigOption { } } -// WithSynopsis 少于一行的操作说明 -func WithSynopsis(v string) ConfigOption { +// WithShort 少于一行的操作说明 +func WithShort(v string) ConfigOption { return func(cc *config) ConfigOption { - previous := cc.Synopsis - cc.Synopsis = v - return WithSynopsis(previous) + previous := cc.Short + cc.Short = v + return WithShort(previous) } } @@ -161,13 +160,14 @@ func newDefaultConfig() *config { for _, opt := range [...]ConfigOption{ WithBind(nil), WithBindFieldPath(make([]string, 0)...), - WithSynopsis(""), + WithShort(""), WithUsage(""), WithExecute(nil), - WithXConfOption(DefaultXConfOption...), - WithParser(Parser), + WithXConfOption(defaultXConfOption...), + WithParser(ParserXConf), WithOnExecuterLost(func(ctx context.Context, c *Command, ff *flag.FlagSet, args []string) error { - return c.wrapErr(errors.New("no executer")) + c.Usage() + return ErrHelp }), } { opt(cc) @@ -179,7 +179,7 @@ func newDefaultConfig() *config { // all getter func func (cc *config) GetBind() interface{} { return cc.Bind } func (cc *config) GetBindFieldPath() []string { return cc.BindFieldPath } -func (cc *config) GetSynopsis() string { return cc.Synopsis } +func (cc *config) GetShort() string { return cc.Short } func (cc *config) GetUsage() string { return cc.Usage } func (cc *config) GetExecute() Executer { return cc.Execute } func (cc *config) GetXConfOption() []xconf.Option { return cc.XConfOption } @@ -190,7 +190,7 @@ func (cc *config) GetOnExecuterLost() Executer { return cc.OnExecuterLost } type ConfigVisitor interface { GetBind() interface{} GetBindFieldPath() []string - GetSynopsis() string + GetShort() string GetUsage() string GetExecute() Executer GetXConfOption() []xconf.Option diff --git a/xcmd/main/main.go b/xcmd/main/main.go index 29f3da0..5d6315d 100644 --- a/xcmd/main/main.go +++ b/xcmd/main/main.go @@ -22,7 +22,7 @@ func main() { // export 派生go命令,只绑定http_address字段 // go 派生export命令,追加绑定timeouts字段 xcmd.SubCommand("export", - xcmd.WithSynopsis("export proto to golang/cs/python/lua"), + xcmd.WithShort("export proto to golang/cs/python/lua"), xcmd.WithExecute(func(ctx context.Context, c *xcmd.Command, ff *flag.FlagSet, args []string) error { fmt.Println("export command") return nil @@ -32,7 +32,7 @@ func main() { }). SubCommand("go", xcmd.WithBindFieldPath("http_address"), - xcmd.WithSynopsis("generate golang code"), + xcmd.WithShort("generate golang code"), ).Use(func(ctx context.Context, c *xcmd.Command, ff *flag.FlagSet, args []string, next xcmd.Executer) error { return next(ctx, c, ff, args) }). @@ -42,7 +42,7 @@ func main() { anotherBind := xcmdtest.NewLog() xcmd.AddCommand(xcmd.NewCommand("log", xcmd.WithBind(anotherBind), - xcmd.WithSynopsis("log command"), + xcmd.WithShort("log command"), xcmd.WithExecute(func(ctx context.Context, c *xcmd.Command, ff *flag.FlagSet, args []string) error { fmt.Println("log command") return nil @@ -50,7 +50,7 @@ func main() { // sub命令同样绑定到xcmdtest.Config实例cc xcmd.AddCommand(xcmd.NewCommand("layout", xcmd.WithBind(cc), - xcmd.WithSynopsis("layout command"), + xcmd.WithShort("layout command"), xcmd.WithExecute(func(ctx context.Context, c *xcmd.Command, ff *flag.FlagSet, args []string) error { fmt.Println("layout command") return nil @@ -65,7 +65,7 @@ func main() { manual := xcmd.NewCommand("manual", xcmd.WithBind(cc), - xcmd.WithSynopsis("manual bing flag"), + xcmd.WithShort("manual bing flag"), xcmd.WithExecute(func(ctx context.Context, c *xcmd.Command, ff *flag.FlagSet, args []string) error { fmt.Println("manual command got log_level:", logLevel) return nil @@ -75,7 +75,6 @@ func main() { // panicPrintErr("comamnd check with err: %v", xcmd.Check()) panicPrintErr("comamnd Execute with err: %v", xcmd.Execute(context.Background(), os.Args[1:]...)) - } func panicPrintErr(format string, err error) { diff --git a/xcmd/option.go b/xcmd/option.go index bdc92d5..7afa586 100644 --- a/xcmd/option.go +++ b/xcmd/option.go @@ -2,20 +2,18 @@ package xcmd import ( "context" - "errors" "flag" - "fmt" - "reflect" - "strings" "github.com/sandwich-go/xconf" - "github.com/sandwich-go/xconf/xflag" - "github.com/sandwich-go/xconf/xutil" ) +// Executer 命令执行方法 type Executer = func(ctx context.Context, c *Command, ff *flag.FlagSet, args []string) error -var DefaultXConfOption = []xconf.Option{ +// SetDefaultXConfOption 设定默认的XConf参数项 +func SetDefaultXConfOption(opts ...xconf.Option) { defaultXConfOption = opts } + +var defaultXConfOption = []xconf.Option{ xconf.WithErrorHandling(xconf.ContinueOnError), xconf.WithReplaceFlagSetUsage(false), } @@ -27,89 +25,22 @@ func configOptionDeclareWithDefault() interface{} { "Bind": (interface{})(nil), // annotation@BindFieldPath(comment="命令绑定的参数FieldPath,如空则全部绑定") "BindFieldPath": []string{}, - // annotation@Synopsis(comment="少于一行的操作说明") - "Synopsis": "", + // annotation@Short(comment="少于一行的操作说明") + "Short": "", // annotation@Usage(comment="详细说明") "Usage": "", // annotation@Execute(comment="执行方法") "Execute": Executer(nil), // annotation@XConfOption(comment="Parser依赖的XConf配置") - "XConfOption": ([]xconf.Option)(DefaultXConfOption), + "XConfOption": ([]xconf.Option)(defaultXConfOption), // annotation@Parser(comment="配置解析") - "Parser": MiddlewareFunc(Parser), - // annotation@Executer(comment="配置解析") + "Parser": MiddlewareFunc(ParserXConf), + // annotation@Executer(comment="当未配置Parser时触发该默认逻辑") "OnExecuterLost": Executer(func(ctx context.Context, c *Command, ff *flag.FlagSet, args []string) error { - return c.wrapErr(errors.New("no executer")) + c.Usage() + return ErrHelp }), } } var _ = configOptionDeclareWithDefault - -func Parser(ctx context.Context, c *Command, ff *flag.FlagSet, args []string, next Executer) error { - // 默认 usage 无参 - ff.Usage = func() { - c.Explain(c.Output) - xflag.PrintDefaults(ff) - } - if c.cc.GetBind() == nil { - return ff.Parse(args) - } - cc := xconf.NewOptions( - xconf.WithErrorHandling(xconf.ContinueOnError), - xconf.WithFlagSet(ff), - xconf.WithFlagArgs(args...)) - cc.ApplyOption(c.cc.GetXConfOption()...) - - // 获取bindto结构合法的FieldPath,并过滤合法的BindToFieldPath - _, fieldsMap := xconf.NewStruct( - reflect.New(reflect.ValueOf(c.cc.GetBind()).Type().Elem()).Interface(), - cc.TagName, - cc.TagNameForDefaultValue, - cc.FieldTagConvertor).Map() - - var ignorePath []string - var invalidKeys []string - - if len(c.cc.GetBindFieldPath()) > 0 { - for k := range fieldsMap { - if !xutil.ContainStringEqualFold(c.cc.GetBindFieldPath(), k) { - ignorePath = append(ignorePath, k) - } - } - } - for _, v := range c.cc.GetBindFieldPath() { - if _, ok := fieldsMap[v]; !ok { - invalidKeys = append(invalidKeys, v) - } - } - - if len(invalidKeys) > 0 { - var keys []string - for k := range fieldsMap { - keys = append(keys, k) - } - return c.wrapErr(fmt.Errorf("option BindFieldPath has invalid item: %s valid: %v", strings.Join(invalidKeys, ","), keys)) - } - - cc.ApplyOption(xconf.WithFlagCreateIgnoreFiledPath(ignorePath...)) - x := xconf.NewWithConf(cc) - - // Available Commands + Flags - cc.FlagSet.Usage = func() { - c.Explain(c.Output) - x.UsageToWriter(c.Output, args...) - } - err := x.Parse(c.cc.GetBind()) - if err != nil { - if IsErrHelp(err) { - err = nil - } else { - err = fmt.Errorf("[ApplyArgs] %s", err.Error()) - } - } - if err != nil { - return err - } - return next(ctx, c, ff, args) -} diff --git a/xcmd/parser_xconf.go b/xcmd/parser_xconf.go new file mode 100644 index 0000000..4fc9ba1 --- /dev/null +++ b/xcmd/parser_xconf.go @@ -0,0 +1,64 @@ +package xcmd + +import ( + "context" + "flag" + "fmt" + "strings" + + "github.com/sandwich-go/xconf" + "github.com/sandwich-go/xconf/xutil" +) + +// ParserXConf xconf的Parser方法 +func ParserXConf(ctx context.Context, c *Command, ff *flag.FlagSet, args []string, next Executer) error { + cc := xconf.NewOptions( + xconf.WithErrorHandling(xconf.ContinueOnError), + xconf.WithFlagSet(ff), + xconf.WithFlagArgs(args...)) + cc.ApplyOption(c.cc.GetXConfOption()...) + + x := xconf.NewWithConf(cc) + keysList := xconf.FieldPathList(c.cc.GetBind(), x) + + // keysList中的元素如果没有包含在GetBindFieldPath中,则为不允许通过flag覆盖的item + var ignorePath []string + if len(c.cc.GetBindFieldPath()) > 0 { + for _, k := range keysList { + if !xutil.ContainStringEqualFold(c.cc.GetBindFieldPath(), k) { + ignorePath = append(ignorePath, k) + } + } + } + // 检查GetBindFieldPath中的key是否合法 + var invalidKeys []string + for _, v := range c.cc.GetBindFieldPath() { + if !xutil.ContainString(keysList, v) { + invalidKeys = append(invalidKeys, v) + } + } + if len(invalidKeys) > 0 { + return c.wrapErr(fmt.Errorf("option BindFieldPath has invalid item: %s valid: %v", strings.Join(invalidKeys, ","), keysList)) + } + // 更新忽略调的绑定字段,重新狗仔xconf实例 + cc.ApplyOption(xconf.WithFlagCreateIgnoreFiledPath(ignorePath...)) + x = xconf.NewWithConf(cc) + + // 更新FlagSet的Usage,使用xconf内置版本 + cc.FlagSet.Usage = func() { + c.Explain(c.Output) + x.UsageToWriter(c.Output, args...) + } + err := x.Parse(c.cc.GetBind()) + if err != nil { + if IsErrHelp(err) { + err = ErrHelp + } else { + err = fmt.Errorf("[ParserXConf] %s", err.Error()) + } + } + if err != nil { + return err + } + return next(ctx, c, ff, args) +} diff --git a/xconf.go b/xconf.go index 0203348..ef8f454 100644 --- a/xconf.go +++ b/xconf.go @@ -125,6 +125,25 @@ func (x *XConf) runningLogData(name string, data map[string]interface{}) { x.runningLogger(fmt.Sprintf("===========================> Data %s\n%v\n", name, data)) } +// FieldMap 获取对象的Field对象Map +func FieldMap(valPtr interface{}, x *XConf) map[string]StructFieldPathInfo { + // 获取bindto结构合法的FieldPath,并过滤合法的BindToFieldPath + _, fieldsMap := NewStruct( + reflect.New(reflect.ValueOf(valPtr).Type().Elem()).Interface(), + x.cc.TagName, + x.cc.TagNameForDefaultValue, + x.cc.FieldTagConvertor).Map() + return fieldsMap +} + +// FieldPathList 获取对象的FieldPath列表 +func FieldPathList(valPtr interface{}, x *XConf) (ret []string) { + for k := range FieldMap(valPtr, x) { + ret = append(ret, k) + } + return ret +} + func (x *XConf) keysList() []string { var keys []string for k := range x.fieldPathInfoMap { diff --git a/xflag/templates/xmap/map.go b/xflag/templates/xmap/map.go index 9eb4965..bb6df3f 100644 --- a/xflag/templates/xmap/map.go +++ b/xflag/templates/xmap/map.go @@ -77,6 +77,10 @@ func (e *MapKTypeVType) Set(s string) error { e.s = s kv := strings.Split(s, StringValueDelim) if len(kv)%2 == 1 { + // 设定了default标签或者空的字符串 + if len(kv) == 1 && kv[0] == "" { + return nil + } return errors.New("got the odd number of input pairs") } if !e.set { diff --git a/xflag/vars/gen_MapInt64Int64.go b/xflag/vars/gen_MapInt64Int64.go index 085f351..a47c609 100644 --- a/xflag/vars/gen_MapInt64Int64.go +++ b/xflag/vars/gen_MapInt64Int64.go @@ -73,6 +73,10 @@ func (e *MapInt64Int64) Set(s string) error { e.s = s kv := strings.Split(s, StringValueDelim) if len(kv)%2 == 1 { + // 设定了default标签或者空的字符串 + if len(kv) == 1 && kv[0] == "" { + return nil + } return errors.New("got the odd number of input pairs") } if !e.set { diff --git a/xflag/vars/gen_MapInt64String.go b/xflag/vars/gen_MapInt64String.go index 813f310..23067a6 100644 --- a/xflag/vars/gen_MapInt64String.go +++ b/xflag/vars/gen_MapInt64String.go @@ -73,6 +73,10 @@ func (e *MapInt64String) Set(s string) error { e.s = s kv := strings.Split(s, StringValueDelim) if len(kv)%2 == 1 { + // 设定了default标签或者空的字符串 + if len(kv) == 1 && kv[0] == "" { + return nil + } return errors.New("got the odd number of input pairs") } if !e.set { diff --git a/xflag/vars/gen_MapIntInt.go b/xflag/vars/gen_MapIntInt.go index 4353e18..4f204de 100644 --- a/xflag/vars/gen_MapIntInt.go +++ b/xflag/vars/gen_MapIntInt.go @@ -73,6 +73,10 @@ func (e *MapIntInt) Set(s string) error { e.s = s kv := strings.Split(s, StringValueDelim) if len(kv)%2 == 1 { + // 设定了default标签或者空的字符串 + if len(kv) == 1 && kv[0] == "" { + return nil + } return errors.New("got the odd number of input pairs") } if !e.set { diff --git a/xflag/vars/gen_MapIntString.go b/xflag/vars/gen_MapIntString.go index 12c5160..183b638 100644 --- a/xflag/vars/gen_MapIntString.go +++ b/xflag/vars/gen_MapIntString.go @@ -73,6 +73,10 @@ func (e *MapIntString) Set(s string) error { e.s = s kv := strings.Split(s, StringValueDelim) if len(kv)%2 == 1 { + // 设定了default标签或者空的字符串 + if len(kv) == 1 && kv[0] == "" { + return nil + } return errors.New("got the odd number of input pairs") } if !e.set { diff --git a/xflag/vars/gen_MapStringInt.go b/xflag/vars/gen_MapStringInt.go index fe996c9..b34cd9c 100644 --- a/xflag/vars/gen_MapStringInt.go +++ b/xflag/vars/gen_MapStringInt.go @@ -73,6 +73,10 @@ func (e *MapStringInt) Set(s string) error { e.s = s kv := strings.Split(s, StringValueDelim) if len(kv)%2 == 1 { + // 设定了default标签或者空的字符串 + if len(kv) == 1 && kv[0] == "" { + return nil + } return errors.New("got the odd number of input pairs") } if !e.set { diff --git a/xflag/vars/gen_MapStringInt64.go b/xflag/vars/gen_MapStringInt64.go index d40b84b..db30e99 100644 --- a/xflag/vars/gen_MapStringInt64.go +++ b/xflag/vars/gen_MapStringInt64.go @@ -73,6 +73,10 @@ func (e *MapStringInt64) Set(s string) error { e.s = s kv := strings.Split(s, StringValueDelim) if len(kv)%2 == 1 { + // 设定了default标签或者空的字符串 + if len(kv) == 1 && kv[0] == "" { + return nil + } return errors.New("got the odd number of input pairs") } if !e.set { diff --git a/xflag/vars/gen_MapStringString.go b/xflag/vars/gen_MapStringString.go index fb31ad7..bbf1028 100644 --- a/xflag/vars/gen_MapStringString.go +++ b/xflag/vars/gen_MapStringString.go @@ -73,6 +73,10 @@ func (e *MapStringString) Set(s string) error { e.s = s kv := strings.Split(s, StringValueDelim) if len(kv)%2 == 1 { + // 设定了default标签或者空的字符串 + if len(kv) == 1 && kv[0] == "" { + return nil + } return errors.New("got the odd number of input pairs") } if !e.set { diff --git a/xflag/vars/gen_MapStringTimeDuration.go b/xflag/vars/gen_MapStringTimeDuration.go index 3676d49..fc660d6 100644 --- a/xflag/vars/gen_MapStringTimeDuration.go +++ b/xflag/vars/gen_MapStringTimeDuration.go @@ -74,6 +74,10 @@ func (e *MapStringTimeDuration) Set(s string) error { e.s = s kv := strings.Split(s, StringValueDelim) if len(kv)%2 == 1 { + // 设定了default标签或者空的字符串 + if len(kv) == 1 && kv[0] == "" { + return nil + } return errors.New("got the odd number of input pairs") } if !e.set {