diff --git a/app_test.go b/app_test.go index d98e311..54b247a 100644 --- a/app_test.go +++ b/app_test.go @@ -22,7 +22,7 @@ var ( Desc: "an simple command", Func: func(c *gcli.Command, args []string) error { dump.Println(c.Path(), args) - c.SetValue("simple", "simple command") + c.Set("simple", "simple command") return nil }, } @@ -45,7 +45,7 @@ var ( Name: "sub1", Desc: "desc for top1.sub1", Func: func(c *gcli.Command, args []string) error { - c.SetValue("msg", c.App().GetVal("top1:sub1")) + c.Set("msg", c.App().Get("top1:sub1")) return nil }, }, @@ -291,12 +291,12 @@ func TestApp_Run_subcommand(t *testing.T) { is := assert.New(t) id := "top1:sub1" - appWithMl.SetValue(id, "TestApp_Run_subcommand") + appWithMl.Set(id, "TestApp_Run_subcommand") appWithMl.Run([]string{"top1", "sub1"}) c := appWithMl.FindCommand(id) is.NotEmpty(c) - is.Eq("TestApp_Run_subcommand", c.GetVal("msg")) + is.Eq("TestApp_Run_subcommand", c.Get("msg")) } func TestApp_Run_by_cmd_ID(t *testing.T) { @@ -452,6 +452,6 @@ func (uc *UserCommand) Config(c *gcli.Command) { c.StrOpt(&uc.opt1, "opt", "o", "", "desc") } -func (uc *UserCommand) Execute(c *gcli.Command, args []string) error { +func (uc *UserCommand) Execute(_ *gcli.Command, _ []string) error { return nil } diff --git a/base.go b/base.go index 704dce8..3aca706 100644 --- a/base.go +++ b/base.go @@ -125,6 +125,16 @@ func (ctx *Context) hasHelpKeywords() bool { return strings.HasSuffix(ctx.argLine, " -h") || strings.HasSuffix(ctx.argLine, " --help") } +// SetValue to ctx +func (ctx *Context) SetValue(key string, val any) { + ctx.Set(key, val) +} + +// GetVal from ctx +func (ctx *Context) GetVal(key string) interface{} { + return ctx.Get(key) +} + /************************************************************* * command Base *************************************************************/ diff --git a/cmd.go b/cmd.go index 0de2f4f..0d2cf8f 100644 --- a/cmd.go +++ b/cmd.go @@ -9,6 +9,7 @@ import ( "github.com/gookit/color" "github.com/gookit/gcli/v3/events" + "github.com/gookit/gcli/v3/gflag" "github.com/gookit/gcli/v3/helper" "github.com/gookit/goutil/arrutil" "github.com/gookit/goutil/structs" @@ -50,12 +51,10 @@ type Command struct { // --- provide option and argument parse and binding. - // Flags options for the command - Flags - // Arguments for the command - Arguments + // Flags (options+arguments) for the command + gflag.Flags - // Name is the full command name. + // Name is the command name. Name string // Desc is the command description message. Desc string diff --git a/gcli.go b/gcli.go index d056688..d9bd957 100644 --- a/gcli.go +++ b/gcli.go @@ -76,6 +76,17 @@ func NewFlags(nameWithDesc ...string) *gflag.Flags { return gflag.New(nameWithDesc...) } +// Argument alias of the gflag.Argument +type Argument = gflag.Argument + +// Arguments alias of the gflag.Arguments +type Arguments = gflag.Arguments + +// NewArgument quick create a new command argument +func NewArgument(name, desc string, requiredAndArrayed ...bool) *Argument { + return gflag.NewArg(name, desc, nil, requiredAndArrayed...) +} + /************************************************************************* * global options *************************************************************************/ diff --git a/gargs.go b/gflag/args.go similarity index 93% rename from gargs.go rename to gflag/args.go index 77eadab..bc57e99 100644 --- a/gargs.go +++ b/gflag/args.go @@ -1,9 +1,8 @@ -package gcli +package gflag import ( "strings" - "github.com/gookit/gcli/v3/gflag" "github.com/gookit/gcli/v3/helper" "github.com/gookit/goutil/errorx" "github.com/gookit/goutil/structs" @@ -20,10 +19,10 @@ type Arguments struct { name string // args definition for a command. // - // eg. { + // eg. [ // {"arg0", "this is first argument", false, false}, // {"arg1", "this is second argument", false, false}, - // } + // ] args []*Argument // record min length for args // argsMinLen int @@ -110,7 +109,7 @@ func (ags *Arguments) AddArg(name, desc string, requiredAndArrayed ...bool) *Arg // AddArgByRule add an arg by simple string rule func (ags *Arguments) AddArgByRule(name, rule string) *Argument { - mp := gflag.ParseSimpleRule(name, rule) + mp := ParseSimpleRule(name, rule) required := strutil.QuietBool(mp["required"]) newArg := NewArgument(name, mp["desc"], required) @@ -141,15 +140,15 @@ func (ags *Arguments) AddArgument(arg *Argument) *Argument { // validate argument name name := arg.goodArgument() if _, has := ags.argsIndexes[name]; has { - panicf("the argument name '%s' already exists in command '%s'", name, ags.name) + helper.Panicf("the argument name '%s' already exists in command '%s'", name, ags.name) } if ags.hasArrayArg { - panicf("have defined an array argument, you cannot add argument '%s'", name) + helper.Panicf("have defined an array argument, you cannot add argument '%s'", name) } if arg.Required && ags.hasOptionalArg { - panicf("required argument '%s' cannot be defined after optional argument", name) + helper.Panicf("required argument '%s' cannot be defined after optional argument", name) } // add argument index record @@ -200,7 +199,7 @@ func (ags *Arguments) HasArguments() bool { func (ags *Arguments) Arg(name string) *Argument { i, ok := ags.argsIndexes[name] if !ok { - panicf("get not exists argument '%s'", name) + helper.Panicf("get not exists argument '%s'", name) } return ags.args[i] } @@ -208,7 +207,7 @@ func (ags *Arguments) Arg(name string) *Argument { // ArgByIndex get named arg by index func (ags *Arguments) ArgByIndex(i int) *Argument { if i >= len(ags.args) { - panicf("get not exists argument #%d", i) + helper.Panicf("get not exists argument #%d", i) } return ags.args[i] } @@ -308,11 +307,11 @@ func (a *Argument) Init() *Argument { func (a *Argument) goodArgument() string { name := strings.TrimSpace(a.Name) if name == "" { - panicf("the command argument name cannot be empty") + helper.Panicf("the command argument name cannot be empty") } if !helper.IsGoodName(name) { - panicf("the argument name '%s' is invalid, must match: %s", name, helper.RegGoodName) + helper.Panicf("the argument name '%s' is invalid, must match: %s", name, helper.RegGoodName) } a.Name = name diff --git a/gargs_test.go b/gflag/args_test.go similarity index 96% rename from gargs_test.go rename to gflag/args_test.go index 1453370..e618f56 100644 --- a/gargs_test.go +++ b/gflag/args_test.go @@ -1,4 +1,4 @@ -package gcli_test +package gflag_test import ( "strconv" @@ -6,6 +6,7 @@ import ( "testing" "github.com/gookit/gcli/v3" + "github.com/gookit/gcli/v3/gflag" "github.com/gookit/goutil/testutil/assert" ) @@ -124,7 +125,7 @@ func TestArgument(t *testing.T) { is.Eq([]string{"a", "b"}, arg.Array()) // required and is-array - arg = gcli.NewArgument("arg1", "arg desc", true, true) + arg = gflag.NewArgument("arg1", "arg desc", true, true) arg.Init() is.True(arg.Arrayed) is.True(arg.Required) @@ -149,7 +150,7 @@ var str2int = func(val any) (any, error) { func TestArgument_WithConfig(t *testing.T) { arg := gcli.NewArgument("arg0", "arg desc").WithFn(func(arg *gcli.Argument) { - arg.SetValue(23) + _ = arg.SetValue(23) arg.Init() }) diff --git a/gflag/flags.go b/gflag/flags.go new file mode 100644 index 0000000..577e82e --- /dev/null +++ b/gflag/flags.go @@ -0,0 +1,855 @@ +package gflag + +import ( + "bytes" + "errors" + "flag" + "fmt" + "io" + "os" + "reflect" + "strings" + "unsafe" + + "github.com/gookit/color" + "github.com/gookit/gcli/v3/helper" + "github.com/gookit/goutil/cflag" + "github.com/gookit/goutil/mathutil" + "github.com/gookit/goutil/strutil" +) + +// Flags struct definition +type Flags struct { + // Desc message + Desc string + // AfterParse options hook + AfterParse func(fs *Flags) error + + // cfg option for the flags + cfg *FlagsConfig + // the options flag set + fSet *flag.FlagSet + // buf for build help message + buf *bytes.Buffer + // output for print help message + out io.Writer + + // all option names of the command. {name: length} // TODO delete, move len to meta. + names map[string]int + // metadata for all options + metas map[string]*FlagMeta // TODO support option category + // short names map for options. format: {short: name} + // eg. {"n": "name", "o": "opt"} + shorts map[string]string + // support option category + categories []OptCategory + // flag name max length. useful for render help + // eg: "-V, --version" length is 13 + flagMaxLen int + // exist short names. useful for render help + existShort bool + + // --- arguments + Arguments +} + +func newDefaultFlagConfig() *FlagsConfig { + return &FlagsConfig{ + Alignment: AlignLeft, + TagName: FlagTagName, + } +} + +// New create a new Flags +func New(nameWithDesc ...string) *Flags { + fs := &Flags{ + out: os.Stdout, + cfg: newDefaultFlagConfig(), + } + // fs.ExitFunc = os.Exit + + fName := "gflag" + if num := len(nameWithDesc); num > 0 { + fName = nameWithDesc[0] + if num > 1 { + fs.Desc = nameWithDesc[1] + } + } + + fs.InitFlagSet(fName) + return fs +} + +// InitFlagSet create and init flag.FlagSet +func (fs *Flags) InitFlagSet(name string) { + if fs.fSet != nil { + return + } + + if fs.cfg == nil { + fs.cfg = newDefaultFlagConfig() + } + + fs.fSet = flag.NewFlagSet(name, flag.ContinueOnError) + // disable output internal error message on parse flags + fs.fSet.SetOutput(io.Discard) + // nothing to do ... render usage on after parsed + fs.fSet.Usage = func() {} +} + +// SetConfig for the object. +func (fs *Flags) SetConfig(opt *FlagsConfig) { fs.cfg = opt } + +// UseSimpleRule for the parse tag value rule string. see TagRuleSimple +func (fs *Flags) UseSimpleRule() *Flags { + fs.cfg.TagRuleType = TagRuleSimple + return fs +} + +// WithConfigFn for the object. +func (fs *Flags) WithConfigFn(fns ...func(cfg *FlagsConfig)) *Flags { + for _, fn := range fns { + fn(fs.cfg) + } + return fs +} + +/*********************************************************************** + * Flags: + * - parse input flags + ***********************************************************************/ + +// Run flags parse and handle help render +// +// Usage: +// +// gf := gflag.New() +// ... +// gf.Run(os.Args) +func (fs *Flags) Run(args []string) { + if args == nil { + args = os.Args + } + + // split binFile and args + binFile, waitArgs := args[0], args[1:] + + // register help render + fs.SetHelpRender(func() { + if fs.Desc != "" { + color.Infoln(fs.Desc) + } + + color.Comment.Println("Usage:") + color.Cyan.Println(" ", binFile, "[--OPTIONS...]\n") + color.Comment.Println("Options:") + + fs.PrintHelpPanel() + }) + + // do parsing + if err := fs.Parse(waitArgs); err != nil { + if err == flag.ErrHelp { + return // ignore help error + } + + color.Errorf("Parse error: %s\n", err.Error()) + } +} + +// Parse given arguments +// +// Usage: +// +// gf := gflag.New() +// gf.BoolOpt(&debug, "debug", "", defDebug, "open debug mode") +// gf.UintOpt(&port, "port", "p", 18081, "the http server port") +// +// err := gf.Parse(os.Args[1:]) +func (fs *Flags) Parse(args []string) (err error) { + defer func() { + if err := recover(); err != nil { + color.Errorln("Flags.Parse Error:", err) + } + }() + + // prepare + if err := fs.prepare(); err != nil { + return err + } + + if len(fs.shorts) > 0 && len(args) > 0 { + args = cflag.ReplaceShorts(args, fs.shorts) + // TODO gcli.Debugf("replace shortcuts. now, args: %v", args) + } + + // do parsing + if err = fs.fSet.Parse(args); err != nil { + return err + } + + // after hook + if fs.AfterParse != nil { + if err := fs.AfterParse(fs); err != nil { + return err + } + } + + // call flags validate + for name, meta := range fs.metas { + fItem := fs.fSet.Lookup(name) + err = meta.Validate(fItem.Value.String()) + if err != nil { + return err + } + } + return +} + +func (fs *Flags) prepare() error { + return nil +} + +/*********************************************************************** + * Flags: + * - binding option from struct + ***********************************************************************/ + +var ( + flagValueType = reflect.TypeOf(new(flag.Value)).Elem() + errNotPtrValue = errors.New("must provide an ptr value") + errNotAnStruct = errors.New("must provide an struct ptr") + errTagRuleType = errors.New("invalid tag rule type on struct") +) + +// FromStruct from struct tag binding options +func (fs *Flags) FromStruct(ptr any) error { + v := reflect.ValueOf(ptr) + if v.Kind() != reflect.Ptr { + return errNotPtrValue + } + + if !v.IsNil() { + v = v.Elem() + } + + t := v.Type() + if t.Kind() != reflect.Struct { + return errNotAnStruct + } + + tagName := fs.cfg.TagName + if tagName == "" { + tagName = FlagTagName + } + + var mp map[string]string + for i := 0; i < t.NumField(); i++ { + sf := t.Field(i) + name := sf.Name + + // skip cannot export field + if name[0] >= 'a' && name[0] <= 'z' { + continue + } + + // eg: "name=int0;shorts=i;required=true;desc=int option message" + str := sf.Tag.Get(tagName) + if str == "" { + continue + } + + fv := v.Field(i) + ft := t.Field(i).Type + if !fv.CanInterface() { + continue + } + + // is pointer + // var isPtr bool + // var isNilPtr bool + if ft.Kind() == reflect.Ptr { + // isPtr = true + if fv.IsNil() { + return fmt.Errorf("field: %s - nil pointer dereference", name) + } + + ft = ft.Elem() + fv = fv.Elem() + } + + if fs.cfg.TagRuleType == TagRuleNamed { + mp = parseNamedRule(name, str) + } else if fs.cfg.TagRuleType == TagRuleSimple { + mp = ParseSimpleRule(name, str) + } else { + return errTagRuleType + } + + // for create flag meta + optName, has := mp["name"] + if !has { // use field as option name. + optName = strutil.SnakeCase(name, "-") + } + + meta := newFlagMeta(optName, mp["desc"], mp["default"], mp["shorts"]) + if must, has := mp["required"]; has { + meta.Required = strutil.MustBool(must) + } + + // field is implements flag.Value + if ft.Implements(flagValueType) { + fs.Var(fv.Interface().(flag.Value), meta) + continue + } + + // get field ptr addr + ptr := unsafe.Pointer(fv.UnsafeAddr()) + switch ft.Kind() { + case reflect.Bool: + fs.BoolVar((*bool)(ptr), meta) + case reflect.Int: + fs.IntVar((*int)(ptr), meta) + // if isNilPtr { + // fv.SetInt(0) + // newPtr := unsafe.Pointer(fv.UnsafeAddr()) + // fs.IntVar((*int)(newPtr), meta) + // } else { + // fs.IntVar((*int)(ptr), meta) + // } + case reflect.Int64: + fs.Int64Var((*int64)(ptr), meta) + case reflect.Uint: + fs.UintVar((*uint)(ptr), meta) + case reflect.Uint64: + fs.Uint64Var((*uint64)(ptr), meta) + case reflect.Float64: + fs.Float64Var((*float64)(ptr), meta) + case reflect.String: + fs.StrVar((*string)(ptr), meta) + default: + return fmt.Errorf("field: %s - invalid type for binding flag", name) + } + } + return nil +} + +/*********************************************************************** + * Flags: + * - binding option var + ***********************************************************************/ + +// --- bool option + +// Bool binding a bool option flag, return pointer +func (fs *Flags) Bool(name, shorts string, defVal bool, desc string) *bool { + meta := newFlagMeta(name, desc, defVal, shorts) + name = fs.checkFlagInfo(meta) + + // binding option to flag.FlagSet + p := fs.fSet.Bool(name, defVal, meta.Desc) + meta.flag = fs.fSet.Lookup(name) + + return p +} + +// BoolVar binding a bool option flag +func (fs *Flags) BoolVar(p *bool, meta *FlagMeta) { fs.boolOpt(p, meta) } + +// BoolOpt binding a bool option +func (fs *Flags) BoolOpt(p *bool, name, shorts string, defVal bool, desc string) { + fs.boolOpt(p, newFlagMeta(name, desc, defVal, shorts)) +} + +// binding option and shorts +func (fs *Flags) boolOpt(p *bool, meta *FlagMeta) { + defVal := meta.DValue().Bool() + name := fs.checkFlagInfo(meta) + + // binding option to flag.FlagSet + fs.fSet.BoolVar(p, name, defVal, meta.Desc) + meta.flag = fs.fSet.Lookup(name) +} + +// --- float option + +// Float64Var binding an float64 option flag +func (fs *Flags) Float64Var(p *float64, meta *FlagMeta) { fs.float64Opt(p, meta) } + +// Float64Opt binding a float64 option +func (fs *Flags) Float64Opt(p *float64, name, shorts string, defVal float64, desc string) { + fs.float64Opt(p, newFlagMeta(name, desc, defVal, shorts)) +} + +func (fs *Flags) float64Opt(p *float64, meta *FlagMeta) { + defVal := meta.DValue().Float64() + name := fs.checkFlagInfo(meta) + + // binding option to flag.FlagSet + fs.fSet.Float64Var(p, name, defVal, meta.Desc) + meta.flag = fs.fSet.Lookup(name) +} + +// --- string option + +// Str binding an string option flag, return pointer +func (fs *Flags) Str(name, shorts string, defValue, desc string) *string { + meta := newFlagMeta(name, desc, defValue, shorts) + name = fs.checkFlagInfo(meta) + + // binding option to flag.FlagSet + p := fs.fSet.String(name, defValue, meta.Desc) + meta.flag = fs.fSet.Lookup(name) + + return p +} + +// StrVar binding an string option flag +func (fs *Flags) StrVar(p *string, meta *FlagMeta) { fs.strOpt(p, meta) } + +// StrOpt binding an string option +func (fs *Flags) StrOpt(p *string, name, shorts, defValue, desc string) { + fs.strOpt(p, newFlagMeta(name, desc, defValue, shorts)) +} + +// binding option and shorts +func (fs *Flags) strOpt(p *string, meta *FlagMeta) { + defVal := meta.DValue().String() + name := fs.checkFlagInfo(meta) + + // binding option to flag.FlagSet + fs.fSet.StringVar(p, meta.Name, defVal, meta.Desc) + meta.flag = fs.fSet.Lookup(name) +} + +// --- intX option + +// Int binding an int option flag, return pointer +func (fs *Flags) Int(name, shorts string, defValue int, desc string) *int { + meta := newFlagMeta(name, desc, defValue, shorts) + name = fs.checkFlagInfo(meta) + + // binding option to flag.FlagSet + p := fs.fSet.Int(name, defValue, meta.Desc) + meta.flag = fs.fSet.Lookup(name) + + return p +} + +// IntVar binding an int option flag +func (fs *Flags) IntVar(p *int, meta *FlagMeta) { fs.intOpt(p, meta) } + +// IntOpt binding an int option +func (fs *Flags) IntOpt(p *int, name, shorts string, defValue int, desc string) { + fs.intOpt(p, newFlagMeta(name, desc, defValue, shorts)) +} + +func (fs *Flags) intOpt(p *int, meta *FlagMeta) { + defValue := meta.DValue().Int() + name := fs.checkFlagInfo(meta) + + // binding option to flag.FlagSet + fs.fSet.IntVar(p, name, defValue, meta.Desc) + meta.flag = fs.fSet.Lookup(name) +} + +// Int64 binding an int64 option flag, return pointer +func (fs *Flags) Int64(name, shorts string, defValue int64, desc string) *int64 { + meta := newFlagMeta(name, desc, defValue, shorts) + name = fs.checkFlagInfo(meta) + + // binding option to flag.FlagSet + p := fs.fSet.Int64(name, defValue, meta.Desc) + meta.flag = fs.fSet.Lookup(name) + + return p +} + +// Int64Var binding an int64 option flag +func (fs *Flags) Int64Var(p *int64, meta *FlagMeta) { fs.int64Opt(p, meta) } + +// Int64Opt binding an int64 option +func (fs *Flags) Int64Opt(p *int64, name, shorts string, defValue int64, desc string) { + fs.int64Opt(p, newFlagMeta(name, desc, defValue, shorts)) +} + +func (fs *Flags) int64Opt(p *int64, meta *FlagMeta) { + defVal := meta.DValue().Int64() + name := fs.checkFlagInfo(meta) + + // binding option to flag.FlagSet + fs.fSet.Int64Var(p, name, defVal, meta.Desc) + meta.flag = fs.fSet.Lookup(name) +} + +// --- uintX option + +// Uint binding an int option flag, return pointer +func (fs *Flags) Uint(name, shorts string, defVal uint, desc string) *uint { + meta := newFlagMeta(name, desc, defVal, shorts) + name = fs.checkFlagInfo(meta) + + // binding option to flag.FlagSet + p := fs.fSet.Uint(name, defVal, meta.Desc) + meta.flag = fs.fSet.Lookup(name) + + return p +} + +// UintVar binding an uint option flag +func (fs *Flags) UintVar(p *uint, meta *FlagMeta) { fs.uintOpt(p, meta) } + +// UintOpt binding an uint option +func (fs *Flags) UintOpt(p *uint, name, shorts string, defValue uint, desc string) { + fs.uintOpt(p, newFlagMeta(name, desc, defValue, shorts)) +} + +func (fs *Flags) uintOpt(p *uint, meta *FlagMeta) { + defVal := meta.DValue().Int() + name := fs.checkFlagInfo(meta) + + // binding option to flag.FlagSet + fs.fSet.UintVar(p, name, uint(defVal), meta.Desc) + meta.flag = fs.fSet.Lookup(name) +} + +// Uint64 binding an int option flag, return pointer +func (fs *Flags) Uint64(name, shorts string, defVal uint64, desc string) *uint64 { + meta := newFlagMeta(name, desc, defVal, shorts) + name = fs.checkFlagInfo(meta) + + p := fs.fSet.Uint64(name, defVal, meta.Desc) + meta.flag = fs.fSet.Lookup(name) + + return p +} + +// Uint64Var binding an uint option flag +func (fs *Flags) Uint64Var(p *uint64, meta *FlagMeta) { fs.uint64Opt(p, meta) } + +// Uint64Opt binding an uint64 option +func (fs *Flags) Uint64Opt(p *uint64, name, shorts string, defVal uint64, desc string) { + fs.uint64Opt(p, newFlagMeta(name, desc, defVal, shorts)) +} + +// binding option and shorts +func (fs *Flags) uint64Opt(p *uint64, meta *FlagMeta) { + defVal := meta.DValue().Int64() + name := fs.checkFlagInfo(meta) + + // binding option to flag.FlagSet + fs.fSet.Uint64Var(p, name, uint64(defVal), meta.Desc) + meta.flag = fs.fSet.Lookup(name) +} + +// Var binding an custom var option flag +func (fs *Flags) Var(p flag.Value, meta *FlagMeta) { fs.varOpt(p, meta) } + +// VarOpt binding a custom var option +// +// Usage: +// +// var names gcli.Strings +// cmd.VarOpt(&names, "tables", "t", "description ...") +func (fs *Flags) VarOpt(p flag.Value, name, shorts, desc string) { + fs.varOpt(p, newFlagMeta(name, desc, nil, shorts)) +} + +// binding option and shorts +func (fs *Flags) varOpt(p flag.Value, meta *FlagMeta) { + name := fs.checkFlagInfo(meta) + + // binding option to flag.FlagSet + fs.fSet.Var(p, name, meta.Desc) + meta.flag = fs.fSet.Lookup(name) +} + +// Required flag option name(s) +func (fs *Flags) Required(names ...string) { + for _, name := range names { + meta, ok := fs.metas[name] + if !ok { + helper.Panicf("undefined option flag '%s'", name) + } + meta.Required = true + } +} + +// check flag option name and short-names +func (fs *Flags) checkFlagInfo(meta *FlagMeta) string { + // NOTICE: must init some required fields + if fs.names == nil { + fs.names = map[string]int{} + fs.metas = map[string]*FlagMeta{} + fs.InitFlagSet("flags-" + meta.Name) + } + + // check flag name + name := meta.initCheck() + if _, ok := fs.metas[name]; ok { + helper.Panicf("redefined option flag '%s'", name) + } + + // is a short name + helpLen := meta.helpNameLen() + // fix: must exclude Hidden option + if !meta.Hidden { + fs.flagMaxLen = mathutil.MaxInt(fs.flagMaxLen, helpLen) + } + + // check short names + fs.checkShortNames(name, meta.Shorts) + + // update name length + fs.names[name] = helpLen + // storage meta and name + fs.metas[name] = meta + return name +} + +// check short names +func (fs *Flags) checkShortNames(name string, shorts []string) { + if len(shorts) == 0 { + return + } + + fs.existShort = true + if fs.shorts == nil { + fs.shorts = map[string]string{} + } + + for _, short := range shorts { + if name == short { + helper.Panicf("short name '%s' has been used as the current option name", short) + } + + if _, ok := fs.names[short]; ok { + helper.Panicf("short name '%s' has been used as an option name", short) + } + + if n, ok := fs.shorts[short]; ok { + helper.Panicf("short name '%s' has been used by option '%s'", short, n) + } + + // storage short name + fs.shorts[short] = name + } + +} + +/*********************************************************************** + * Flags: + * - render help message + ***********************************************************************/ + +// SetHelpRender set the raw *flag.FlagSet.Usage +func (fs *Flags) SetHelpRender(fn func()) { + fs.fSet.Usage = fn +} + +// PrintHelpPanel for all options to the gf.out +func (fs *Flags) PrintHelpPanel() { + color.Fprint(fs.out, fs.String()) +} + +// BuildHelp string for all flag options +func (fs *Flags) BuildHelp() string { + if fs.buf == nil { + fs.buf = new(bytes.Buffer) + } + + // repeat call the method + if fs.buf.Len() < 1 { + fs.FSet().VisitAll(fs.formatOneFlag) + } + + return fs.buf.String() +} + +// String for all flag options +func (fs *Flags) String() string { + return fs.BuildHelp() +} + +func (fs *Flags) formatOneFlag(f *flag.Flag) { + // Skip render: + // - meta is not exists(Has ensured that it is not a short name) + // - it is hidden flag option + // - flag desc is empty + meta, has := fs.metas[f.Name] + if !has || meta.Hidden { + return + } + + var s, fullName string + name := f.Name + // eg: "-V, --version" length is: 13 + nameLen := fs.names[name] + // display description on new line + descNl := fs.cfg.DescNewline + + var nlIndent string + if descNl { + nlIndent = "\n " + } else { + nlIndent = "\n " + strings.Repeat(" ", fs.flagMaxLen) + } + + // add prefix '-' to option + fullName = cflag.AddPrefixes2(name, meta.Shorts, true) + s = fmt.Sprintf(" %s", fullName) + + // - build flag type info + typeName, desc := flag.UnquoteUsage(f) + // typeName: option value data type: int, string, ..., bool value will return "" + if !fs.cfg.WithoutType && len(typeName) > 0 { + typeLen := len(typeName) + 1 + if !descNl && nameLen+typeLen > fs.flagMaxLen { + descNl = true + } else { + nameLen += typeLen + } + + s += fmt.Sprintf(" %s", typeName) + } + + if descNl { + s += nlIndent + } else { + // padding space to flagMaxLen width. + if padLen := fs.flagMaxLen - nameLen; padLen > 0 { + s += strings.Repeat(" ", padLen) + } + s += " " + } + + // --- build description + if desc == "" { + desc = defaultDesc + } + + // flag is required + if meta.Required { + s += "*" + } + + s += strings.Replace(strutil.UpperFirst(desc), "\n", nlIndent, -1) + + // ---- append default value + if isZero, isStr := cflag.IsZeroValue(f, f.DefValue); !isZero { + if isStr { + s += fmt.Sprintf(" (default %q)", f.DefValue) + } else { + s += fmt.Sprintf(" (default %v)", f.DefValue) + } + } + + // save to buffer + fs.buf.WriteString(s) + fs.buf.WriteByte('\n') +} + +/*********************************************************************** + * Flags: + * - helper methods + ***********************************************************************/ + +// IterAll Iteration all flag options with metadata +func (fs *Flags) IterAll(fn func(f *flag.Flag, meta *FlagMeta)) { + fs.FSet().VisitAll(func(f *flag.Flag) { + if _, ok := fs.metas[f.Name]; ok { + fn(f, fs.metas[f.Name]) + } + }) +} + +// ShortNames get all short-names of the option +func (fs *Flags) ShortNames(name string) (ss []string) { + if opt, ok := fs.metas[name]; ok { + ss = opt.Shorts + } + return +} + +// IsShortOpt alias of the IsShortcut() +func (fs *Flags) IsShortOpt(short string) bool { return fs.IsShortName(short) } + +// IsShortName check it is a shortcut name +func (fs *Flags) IsShortName(short string) bool { + if len(short) != 1 { + return false + } + + _, ok := fs.shorts[short] + return ok +} + +// IsOption check it is a option name +func (fs *Flags) IsOption(name string) bool { return fs.HasOption(name) } + +// HasOption check it is a option name +func (fs *Flags) HasOption(name string) bool { + _, ok := fs.names[name] + return ok +} + +// HasFlag check it is a option name. alias of HasOption() +func (fs *Flags) HasFlag(name string) bool { + _, ok := fs.names[name] + return ok +} + +// HasFlagMeta check it is has FlagMeta +func (fs *Flags) HasFlagMeta(name string) bool { + _, ok := fs.metas[name] + return ok +} + +// LookupFlag get flag.Flag by name +func (fs *Flags) LookupFlag(name string) *flag.Flag { return fs.fSet.Lookup(name) } + +// FlagMeta get FlagMeta by name +func (fs *Flags) FlagMeta(name string) *FlagMeta { return fs.metas[name] } + +// Metas get all flag metas +func (fs *Flags) Metas() map[string]*FlagMeta { return fs.metas } + +// Hidden there are given option names +// func (gf *Flags) Hidden(names ...string) { +// for _, name := range names { +// if !gf.HasOption(name) { // not registered +// continue +// } +// +// gf.metas[name].Hidden = true +// } +// } + +// Name of the Flags +func (fs *Flags) Name() string { return fs.fSet.Name() } + +// Len of the Flags +func (fs *Flags) Len() int { return len(fs.names) } + +// FSet get the raw *flag.FlagSet +func (fs *Flags) FSet() *flag.FlagSet { return fs.fSet } + +// SetFlagSet set the raw *flag.FlagSet +func (fs *Flags) SetFlagSet(fSet *flag.FlagSet) { fs.fSet = fSet } + +// SetOutput for the Flags +func (fs *Flags) SetOutput(out io.Writer) { fs.out = out } + +// FlagNames return all option names +func (fs *Flags) FlagNames() map[string]int { return fs.names } + +// RawArg get an argument value by index +func (fs *Flags) RawArg(i int) string { return fs.fSet.Arg(i) } + +// RawArgs get all raw arguments. +// if have been called parse, the return is remaining args. +func (fs *Flags) RawArgs() []string { return fs.fSet.Args() } + +// FSetArgs get all raw arguments. alias of the RawArgs() +// if have been called parse, the return is remaining args. +func (fs *Flags) FSetArgs() []string { return fs.fSet.Args() } diff --git a/gflag/gflag.go b/gflag/gflag.go index 8e45f0d..792a738 100644 --- a/gflag/gflag.go +++ b/gflag/gflag.go @@ -1,22 +1,6 @@ package gflag import ( - "bytes" - "errors" - "flag" - "fmt" - "io" - "os" - "reflect" - "strings" - "unsafe" - - "github.com/gookit/color" - "github.com/gookit/gcli/v3/helper" - "github.com/gookit/goutil/cflag" - "github.com/gookit/goutil/mathutil" - "github.com/gookit/goutil/stdutil" - "github.com/gookit/goutil/structs" "github.com/gookit/goutil/strutil" ) @@ -56,6 +40,8 @@ type FlagsConfig struct { TagName string // TagRuleType for struct tag value. default is TagRuleNamed TagRuleType uint8 + // DisableArg disable binding arguments. + DisableArg bool } // OptCategory struct @@ -63,962 +49,3 @@ type OptCategory struct { Name, Title string OptNames []string } - -// Flags struct definition -type Flags struct { - // Desc message - Desc string - // AfterParse hook - AfterParse func(fs *Flags) error - - // cfg option for the flags - cfg *FlagsConfig - // the options flag set - fSet *flag.FlagSet - // buf for build help message - buf *bytes.Buffer - // output for print help message - out io.Writer - - // all option names of the command. {name: length} // TODO delete, move len to meta. - names map[string]int - // metadata for all options - metas map[string]*FlagMeta // TODO support option category - // short names map for options. format: {short: name} - // eg. {"n": "name", "o": "opt"} - shorts map[string]string - // support option category - categories []OptCategory - // flag name max length. useful for render help - // eg: "-V, --version" length is 13 - flagMaxLen int - // exist short names. useful for render help - existShort bool -} - -func newDefaultFlagConfig() *FlagsConfig { - return &FlagsConfig{ - Alignment: AlignLeft, - TagName: FlagTagName, - } -} - -// New create a new Flags -func New(nameWithDesc ...string) *Flags { - fs := &Flags{ - out: os.Stdout, - cfg: newDefaultFlagConfig(), - } - // fs.ExitFunc = os.Exit - - fName := "gflag" - if num := len(nameWithDesc); num > 0 { - fName = nameWithDesc[0] - if num > 1 { - fs.Desc = nameWithDesc[1] - } - } - - fs.InitFlagSet(fName) - return fs -} - -// InitFlagSet create and init flag.FlagSet -func (fs *Flags) InitFlagSet(name string) { - if fs.fSet != nil { - return - } - - if fs.cfg == nil { - fs.cfg = newDefaultFlagConfig() - } - - fs.fSet = flag.NewFlagSet(name, flag.ContinueOnError) - // disable output internal error message on parse flags - fs.fSet.SetOutput(io.Discard) - // nothing to do ... render usage on after parsed - fs.fSet.Usage = func() {} -} - -// SetConfig for the object. -func (fs *Flags) SetConfig(opt *FlagsConfig) { fs.cfg = opt } - -// UseSimpleRule for the parse tag value rule string. see TagRuleSimple -func (fs *Flags) UseSimpleRule() *Flags { - fs.cfg.TagRuleType = TagRuleSimple - return fs -} - -// WithConfigFn for the object. -func (fs *Flags) WithConfigFn(fns ...func(cfg *FlagsConfig)) *Flags { - for _, fn := range fns { - fn(fs.cfg) - } - return fs -} - -/*********************************************************************** - * Flags: - * - parse input flags - ***********************************************************************/ - -// Run flags parse and handle help render -// -// Usage: -// -// gf := gflag.New() -// ... -// gf.Run(os.Args) -func (fs *Flags) Run(args []string) { - if args == nil { - args = os.Args - } - - // split binFile and args - binFile, waitArgs := args[0], args[1:] - - // register help render - fs.SetHelpRender(func() { - if fs.Desc != "" { - color.Infoln(fs.Desc) - } - - color.Comment.Println("Usage:") - color.Cyan.Println(" ", binFile, "[--OPTIONS...]\n") - color.Comment.Println("Options:") - - fs.PrintHelpPanel() - }) - - // do parsing - if err := fs.Parse(waitArgs); err != nil { - if err == flag.ErrHelp { - return // ignore help error - } - - color.Errorf("Parse error: %s\n", err.Error()) - } -} - -// Parse given arguments -// -// Usage: -// -// gf := gflag.New() -// gf.BoolOpt(&debug, "debug", "", defDebug, "open debug mode") -// gf.UintOpt(&port, "port", "p", 18081, "the http server port") -// -// err := gf.Parse(os.Args[1:]) -func (fs *Flags) Parse(args []string) (err error) { - defer func() { - if err := recover(); err != nil { - color.Errorln("Flags.Parse Error:", err) - } - }() - - // prepare - if err := fs.prepare(); err != nil { - return err - } - - if len(fs.shorts) > 0 && len(args) > 0 { - args = cflag.ReplaceShorts(args, fs.shorts) - // TODO gcli.Debugf("replace shortcuts. now, args: %v", args) - } - - // do parsing - if err = fs.fSet.Parse(args); err != nil { - return err - } - - // after hook - if fs.AfterParse != nil { - if err := fs.AfterParse(fs); err != nil { - return err - } - } - - // call flags validate - for name, meta := range fs.metas { - fItem := fs.fSet.Lookup(name) - err = meta.Validate(fItem.Value.String()) - if err != nil { - return err - } - } - return -} - -func (fs *Flags) prepare() error { - return nil -} - -/*********************************************************************** - * Flags: - * - binding option from struct - ***********************************************************************/ - -var ( - flagValueType = reflect.TypeOf(new(flag.Value)).Elem() - errNotPtrValue = errors.New("must provide an ptr value") - errNotAnStruct = errors.New("must provide an struct ptr") - errTagRuleType = errors.New("invalid tag rule type on struct") -) - -// FromStruct from struct tag binding options -func (fs *Flags) FromStruct(ptr any) error { - v := reflect.ValueOf(ptr) - if v.Kind() != reflect.Ptr { - return errNotPtrValue - } - - if !v.IsNil() { - v = v.Elem() - } - - t := v.Type() - if t.Kind() != reflect.Struct { - return errNotAnStruct - } - - tagName := fs.cfg.TagName - if tagName == "" { - tagName = FlagTagName - } - - var mp map[string]string - for i := 0; i < t.NumField(); i++ { - sf := t.Field(i) - name := sf.Name - - // skip cannot export field - if name[0] >= 'a' && name[0] <= 'z' { - continue - } - - // eg: "name=int0;shorts=i;required=true;desc=int option message" - str := sf.Tag.Get(tagName) - if str == "" { - continue - } - - fv := v.Field(i) - ft := t.Field(i).Type - if !fv.CanInterface() { - continue - } - - // is pointer - // var isPtr bool - // var isNilPtr bool - if ft.Kind() == reflect.Ptr { - // isPtr = true - if fv.IsNil() { - return fmt.Errorf("field: %s - nil pointer dereference", name) - } - - ft = ft.Elem() - fv = fv.Elem() - } - - if fs.cfg.TagRuleType == TagRuleNamed { - mp = parseNamedRule(name, str) - } else if fs.cfg.TagRuleType == TagRuleSimple { - mp = ParseSimpleRule(name, str) - } else { - return errTagRuleType - } - - // for create flag meta - optName, has := mp["name"] - if !has { // use field as option name. - optName = strutil.SnakeCase(name, "-") - } - - meta := newFlagMeta(optName, mp["desc"], mp["default"], mp["shorts"]) - if must, has := mp["required"]; has { - meta.Required = strutil.MustBool(must) - } - - // field is implements flag.Value - if ft.Implements(flagValueType) { - fs.Var(fv.Interface().(flag.Value), meta) - continue - } - - // get field ptr addr - ptr := unsafe.Pointer(fv.UnsafeAddr()) - switch ft.Kind() { - case reflect.Bool: - fs.BoolVar((*bool)(ptr), meta) - case reflect.Int: - fs.IntVar((*int)(ptr), meta) - // if isNilPtr { - // fv.SetInt(0) - // newPtr := unsafe.Pointer(fv.UnsafeAddr()) - // fs.IntVar((*int)(newPtr), meta) - // } else { - // fs.IntVar((*int)(ptr), meta) - // } - case reflect.Int64: - fs.Int64Var((*int64)(ptr), meta) - case reflect.Uint: - fs.UintVar((*uint)(ptr), meta) - case reflect.Uint64: - fs.Uint64Var((*uint64)(ptr), meta) - case reflect.Float64: - fs.Float64Var((*float64)(ptr), meta) - case reflect.String: - fs.StrVar((*string)(ptr), meta) - default: - return fmt.Errorf("field: %s - invalid type for binding flag", name) - } - } - return nil -} - -/*********************************************************************** - * Flags: - * - binding option var - ***********************************************************************/ - -// --- bool option - -// Bool binding a bool option flag, return pointer -func (fs *Flags) Bool(name, shorts string, defVal bool, desc string) *bool { - meta := newFlagMeta(name, desc, defVal, shorts) - name = fs.checkFlagInfo(meta) - - // binding option to flag.FlagSet - p := fs.fSet.Bool(name, defVal, meta.Desc) - meta.flag = fs.fSet.Lookup(name) - - return p -} - -// BoolVar binding a bool option flag -func (fs *Flags) BoolVar(p *bool, meta *FlagMeta) { fs.boolOpt(p, meta) } - -// BoolOpt binding a bool option -func (fs *Flags) BoolOpt(p *bool, name, shorts string, defVal bool, desc string) { - fs.boolOpt(p, newFlagMeta(name, desc, defVal, shorts)) -} - -// binding option and shorts -func (fs *Flags) boolOpt(p *bool, meta *FlagMeta) { - defVal := meta.DValue().Bool() - name := fs.checkFlagInfo(meta) - - // binding option to flag.FlagSet - fs.fSet.BoolVar(p, name, defVal, meta.Desc) - meta.flag = fs.fSet.Lookup(name) -} - -// --- float option - -// Float64Var binding an float64 option flag -func (fs *Flags) Float64Var(p *float64, meta *FlagMeta) { fs.float64Opt(p, meta) } - -// Float64Opt binding a float64 option -func (fs *Flags) Float64Opt(p *float64, name, shorts string, defVal float64, desc string) { - fs.float64Opt(p, newFlagMeta(name, desc, defVal, shorts)) -} - -func (fs *Flags) float64Opt(p *float64, meta *FlagMeta) { - defVal := meta.DValue().Float64() - name := fs.checkFlagInfo(meta) - - // binding option to flag.FlagSet - fs.fSet.Float64Var(p, name, defVal, meta.Desc) - meta.flag = fs.fSet.Lookup(name) -} - -// --- string option - -// Str binding an string option flag, return pointer -func (fs *Flags) Str(name, shorts string, defValue, desc string) *string { - meta := newFlagMeta(name, desc, defValue, shorts) - name = fs.checkFlagInfo(meta) - - // binding option to flag.FlagSet - p := fs.fSet.String(name, defValue, meta.Desc) - meta.flag = fs.fSet.Lookup(name) - - return p -} - -// StrVar binding an string option flag -func (fs *Flags) StrVar(p *string, meta *FlagMeta) { fs.strOpt(p, meta) } - -// StrOpt binding an string option -func (fs *Flags) StrOpt(p *string, name, shorts, defValue, desc string) { - fs.strOpt(p, newFlagMeta(name, desc, defValue, shorts)) -} - -// binding option and shorts -func (fs *Flags) strOpt(p *string, meta *FlagMeta) { - defVal := meta.DValue().String() - name := fs.checkFlagInfo(meta) - - // binding option to flag.FlagSet - fs.fSet.StringVar(p, meta.Name, defVal, meta.Desc) - meta.flag = fs.fSet.Lookup(name) -} - -// --- intX option - -// Int binding an int option flag, return pointer -func (fs *Flags) Int(name, shorts string, defValue int, desc string) *int { - meta := newFlagMeta(name, desc, defValue, shorts) - name = fs.checkFlagInfo(meta) - - // binding option to flag.FlagSet - p := fs.fSet.Int(name, defValue, meta.Desc) - meta.flag = fs.fSet.Lookup(name) - - return p -} - -// IntVar binding an int option flag -func (fs *Flags) IntVar(p *int, meta *FlagMeta) { fs.intOpt(p, meta) } - -// IntOpt binding an int option -func (fs *Flags) IntOpt(p *int, name, shorts string, defValue int, desc string) { - fs.intOpt(p, newFlagMeta(name, desc, defValue, shorts)) -} - -func (fs *Flags) intOpt(p *int, meta *FlagMeta) { - defValue := meta.DValue().Int() - name := fs.checkFlagInfo(meta) - - // binding option to flag.FlagSet - fs.fSet.IntVar(p, name, defValue, meta.Desc) - meta.flag = fs.fSet.Lookup(name) -} - -// Int64 binding an int64 option flag, return pointer -func (fs *Flags) Int64(name, shorts string, defValue int64, desc string) *int64 { - meta := newFlagMeta(name, desc, defValue, shorts) - name = fs.checkFlagInfo(meta) - - // binding option to flag.FlagSet - p := fs.fSet.Int64(name, defValue, meta.Desc) - meta.flag = fs.fSet.Lookup(name) - - return p -} - -// Int64Var binding an int64 option flag -func (fs *Flags) Int64Var(p *int64, meta *FlagMeta) { fs.int64Opt(p, meta) } - -// Int64Opt binding an int64 option -func (fs *Flags) Int64Opt(p *int64, name, shorts string, defValue int64, desc string) { - fs.int64Opt(p, newFlagMeta(name, desc, defValue, shorts)) -} - -func (fs *Flags) int64Opt(p *int64, meta *FlagMeta) { - defVal := meta.DValue().Int64() - name := fs.checkFlagInfo(meta) - - // binding option to flag.FlagSet - fs.fSet.Int64Var(p, name, defVal, meta.Desc) - meta.flag = fs.fSet.Lookup(name) -} - -// --- uintX option - -// Uint binding an int option flag, return pointer -func (fs *Flags) Uint(name, shorts string, defVal uint, desc string) *uint { - meta := newFlagMeta(name, desc, defVal, shorts) - name = fs.checkFlagInfo(meta) - - // binding option to flag.FlagSet - p := fs.fSet.Uint(name, defVal, meta.Desc) - meta.flag = fs.fSet.Lookup(name) - - return p -} - -// UintVar binding an uint option flag -func (fs *Flags) UintVar(p *uint, meta *FlagMeta) { fs.uintOpt(p, meta) } - -// UintOpt binding an uint option -func (fs *Flags) UintOpt(p *uint, name, shorts string, defValue uint, desc string) { - fs.uintOpt(p, newFlagMeta(name, desc, defValue, shorts)) -} - -func (fs *Flags) uintOpt(p *uint, meta *FlagMeta) { - defVal := meta.DValue().Int() - name := fs.checkFlagInfo(meta) - - // binding option to flag.FlagSet - fs.fSet.UintVar(p, name, uint(defVal), meta.Desc) - meta.flag = fs.fSet.Lookup(name) -} - -// Uint64 binding an int option flag, return pointer -func (fs *Flags) Uint64(name, shorts string, defVal uint64, desc string) *uint64 { - meta := newFlagMeta(name, desc, defVal, shorts) - name = fs.checkFlagInfo(meta) - - p := fs.fSet.Uint64(name, defVal, meta.Desc) - meta.flag = fs.fSet.Lookup(name) - - return p -} - -// Uint64Var binding an uint option flag -func (fs *Flags) Uint64Var(p *uint64, meta *FlagMeta) { fs.uint64Opt(p, meta) } - -// Uint64Opt binding an uint64 option -func (fs *Flags) Uint64Opt(p *uint64, name, shorts string, defVal uint64, desc string) { - fs.uint64Opt(p, newFlagMeta(name, desc, defVal, shorts)) -} - -// binding option and shorts -func (fs *Flags) uint64Opt(p *uint64, meta *FlagMeta) { - defVal := meta.DValue().Int64() - name := fs.checkFlagInfo(meta) - - // binding option to flag.FlagSet - fs.fSet.Uint64Var(p, name, uint64(defVal), meta.Desc) - meta.flag = fs.fSet.Lookup(name) -} - -// Var binding an custom var option flag -func (fs *Flags) Var(p flag.Value, meta *FlagMeta) { fs.varOpt(p, meta) } - -// VarOpt binding a custom var option -// -// Usage: -// -// var names gcli.Strings -// cmd.VarOpt(&names, "tables", "t", "description ...") -func (fs *Flags) VarOpt(p flag.Value, name, shorts, desc string) { - fs.varOpt(p, newFlagMeta(name, desc, nil, shorts)) -} - -// binding option and shorts -func (fs *Flags) varOpt(p flag.Value, meta *FlagMeta) { - name := fs.checkFlagInfo(meta) - - // binding option to flag.FlagSet - fs.fSet.Var(p, name, meta.Desc) - meta.flag = fs.fSet.Lookup(name) -} - -// Required flag option name(s) -func (fs *Flags) Required(names ...string) { - for _, name := range names { - meta, ok := fs.metas[name] - if !ok { - helper.Panicf("undefined option flag '%s'", name) - } - meta.Required = true - } -} - -// check flag option name and short-names -func (fs *Flags) checkFlagInfo(meta *FlagMeta) string { - // NOTICE: must init some required fields - if fs.names == nil { - fs.names = map[string]int{} - fs.metas = map[string]*FlagMeta{} - fs.InitFlagSet("flags-" + meta.Name) - } - - // check flag name - name := meta.initCheck() - if _, ok := fs.metas[name]; ok { - helper.Panicf("redefined option flag '%s'", name) - } - - // is a short name - helpLen := meta.helpNameLen() - // fix: must exclude Hidden option - if !meta.Hidden { - fs.flagMaxLen = mathutil.MaxInt(fs.flagMaxLen, helpLen) - } - - // check short names - fs.checkShortNames(name, meta.Shorts) - - // update name length - fs.names[name] = helpLen - // storage meta and name - fs.metas[name] = meta - return name -} - -// check short names -func (fs *Flags) checkShortNames(name string, shorts []string) { - if len(shorts) == 0 { - return - } - - fs.existShort = true - if fs.shorts == nil { - fs.shorts = map[string]string{} - } - - for _, short := range shorts { - if name == short { - helper.Panicf("short name '%s' has been used as the current option name", short) - } - - if _, ok := fs.names[short]; ok { - helper.Panicf("short name '%s' has been used as an option name", short) - } - - if n, ok := fs.shorts[short]; ok { - helper.Panicf("short name '%s' has been used by option '%s'", short, n) - } - - // storage short name - fs.shorts[short] = name - } - -} - -/*********************************************************************** - * Flags: - * - render help message - ***********************************************************************/ - -// SetHelpRender set the raw *flag.FlagSet.Usage -func (fs *Flags) SetHelpRender(fn func()) { - fs.fSet.Usage = fn -} - -// PrintHelpPanel for all options to the gf.out -func (fs *Flags) PrintHelpPanel() { - color.Fprint(fs.out, fs.String()) -} - -// BuildHelp string for all flag options -func (fs *Flags) BuildHelp() string { - if fs.buf == nil { - fs.buf = new(bytes.Buffer) - } - - // repeat call the method - if fs.buf.Len() < 1 { - fs.FSet().VisitAll(fs.formatOneFlag) - } - - return fs.buf.String() -} - -// String for all flag options -func (fs *Flags) String() string { - return fs.BuildHelp() -} - -func (fs *Flags) formatOneFlag(f *flag.Flag) { - // Skip render: - // - meta is not exists(Has ensured that it is not a short name) - // - it is hidden flag option - // - flag desc is empty - meta, has := fs.metas[f.Name] - if !has || meta.Hidden { - return - } - - var s, fullName string - name := f.Name - // eg: "-V, --version" length is: 13 - nameLen := fs.names[name] - // display description on new line - descNl := fs.cfg.DescNewline - - var nlIndent string - if descNl { - nlIndent = "\n " - } else { - nlIndent = "\n " + strings.Repeat(" ", fs.flagMaxLen) - } - - // add prefix '-' to option - fullName = cflag.AddPrefixes2(name, meta.Shorts, true) - s = fmt.Sprintf(" %s", fullName) - - // - build flag type info - typeName, desc := flag.UnquoteUsage(f) - // typeName: option value data type: int, string, ..., bool value will return "" - if !fs.cfg.WithoutType && len(typeName) > 0 { - typeLen := len(typeName) + 1 - if !descNl && nameLen+typeLen > fs.flagMaxLen { - descNl = true - } else { - nameLen += typeLen - } - - s += fmt.Sprintf(" %s", typeName) - } - - if descNl { - s += nlIndent - } else { - // padding space to flagMaxLen width. - if padLen := fs.flagMaxLen - nameLen; padLen > 0 { - s += strings.Repeat(" ", padLen) - } - s += " " - } - - // --- build description - if desc == "" { - desc = defaultDesc - } - - // flag is required - if meta.Required { - s += "*" - } - - s += strings.Replace(strutil.UpperFirst(desc), "\n", nlIndent, -1) - - // ---- append default value - if isZero, isStr := cflag.IsZeroValue(f, f.DefValue); !isZero { - if isStr { - s += fmt.Sprintf(" (default %q)", f.DefValue) - } else { - s += fmt.Sprintf(" (default %v)", f.DefValue) - } - } - - // save to buffer - fs.buf.WriteString(s) - fs.buf.WriteByte('\n') -} - -/*********************************************************************** - * Flags: - * - helper methods - ***********************************************************************/ - -// IterAll Iteration all flag options with metadata -func (fs *Flags) IterAll(fn func(f *flag.Flag, meta *FlagMeta)) { - fs.FSet().VisitAll(func(f *flag.Flag) { - if _, ok := fs.metas[f.Name]; ok { - fn(f, fs.metas[f.Name]) - } - }) -} - -// ShortNames get all short-names of the option -func (fs *Flags) ShortNames(name string) (ss []string) { - if opt, ok := fs.metas[name]; ok { - ss = opt.Shorts - } - return -} - -// IsShortOpt alias of the IsShortcut() -func (fs *Flags) IsShortOpt(short string) bool { return fs.IsShortName(short) } - -// IsShortName check it is a shortcut name -func (fs *Flags) IsShortName(short string) bool { - if len(short) != 1 { - return false - } - - _, ok := fs.shorts[short] - return ok -} - -// IsOption check it is a option name -func (fs *Flags) IsOption(name string) bool { return fs.HasOption(name) } - -// HasOption check it is a option name -func (fs *Flags) HasOption(name string) bool { - _, ok := fs.names[name] - return ok -} - -// HasFlag check it is a option name. alias of HasOption() -func (fs *Flags) HasFlag(name string) bool { - _, ok := fs.names[name] - return ok -} - -// HasFlagMeta check it is has FlagMeta -func (fs *Flags) HasFlagMeta(name string) bool { - _, ok := fs.metas[name] - return ok -} - -// LookupFlag get flag.Flag by name -func (fs *Flags) LookupFlag(name string) *flag.Flag { return fs.fSet.Lookup(name) } - -// FlagMeta get FlagMeta by name -func (fs *Flags) FlagMeta(name string) *FlagMeta { return fs.metas[name] } - -// Metas get all flag metas -func (fs *Flags) Metas() map[string]*FlagMeta { return fs.metas } - -// Hidden there are given option names -// func (gf *Flags) Hidden(names ...string) { -// for _, name := range names { -// if !gf.HasOption(name) { // not registered -// continue -// } -// -// gf.metas[name].Hidden = true -// } -// } - -// Name of the Flags -func (fs *Flags) Name() string { return fs.fSet.Name() } - -// Len of the Flags -func (fs *Flags) Len() int { return len(fs.names) } - -// FSet get the raw *flag.FlagSet -func (fs *Flags) FSet() *flag.FlagSet { return fs.fSet } - -// SetFlagSet set the raw *flag.FlagSet -func (fs *Flags) SetFlagSet(fSet *flag.FlagSet) { fs.fSet = fSet } - -// SetOutput for the Flags -func (fs *Flags) SetOutput(out io.Writer) { fs.out = out } - -// FlagNames return all option names -func (fs *Flags) FlagNames() map[string]int { return fs.names } - -// RawArg get an argument value by index -func (fs *Flags) RawArg(i int) string { return fs.fSet.Arg(i) } - -// RawArgs get all raw arguments. -// if have been called parse, the return is remaining args. -func (fs *Flags) RawArgs() []string { return fs.fSet.Args() } - -// FSetArgs get all raw arguments. alias of the RawArgs() -// if have been called parse, the return is remaining args. -func (fs *Flags) FSetArgs() []string { return fs.fSet.Args() } - -/*********************************************************************** - * Flags: - * - flag metadata - ***********************************************************************/ - -// FlagMeta for an flag(option/argument) -type FlagMeta struct { - // go flag value - flag *flag.Flag - // Name of flag and description - Name, Desc string - // default value for the flag option - DefVal any - // wrapped the default value - defVal *structs.Value - // short names. eg: ["o", "a"] - Shorts []string - // EnvVar allow set flag value from ENV var - EnvVar string - - // --- advanced settings - - // Hidden the option on help - Hidden bool - // Required the option is required - Required bool - // Validator support validate the option flag value - Validator func(val string) error -} - -// newFlagMeta quick create an FlagMeta -func newFlagMeta(name, desc string, defVal any, shortcut string) *FlagMeta { - return &FlagMeta{ - Name: name, - Desc: desc, - // other info - DefVal: defVal, - Shorts: strings.Split(shortcut, ","), - } -} - -func (m *FlagMeta) initCheck() string { - if m.Desc != "" { - desc := strings.Trim(m.Desc, "; ") - if strings.ContainsRune(desc, ';') { - // format: desc;required - // format: desc;required;env TODO parse ENV var - parts := strutil.SplitNTrimmed(desc, ";", 2) - if ln := len(parts); ln > 1 { - bl, err := strutil.Bool(parts[1]) - if err == nil && bl { - desc = parts[0] - m.Required = true - } - } - } - - m.Desc = desc - } - - // filter shorts - if len(m.Shorts) > 0 { - m.Shorts = cflag.FilterNames(m.Shorts) - } - return m.goodName() -} - -// good name of the flag -func (m *FlagMeta) goodName() string { - name := strings.Trim(m.Name, "- ") - if name == "" { - helper.Panicf("option flag name cannot be empty") - } - - if !helper.IsGoodName(name) { - helper.Panicf("option flag name '%s' is invalid", name) - } - - // update self name - m.Name = name - return name -} - -// Shorts2String join shorts to a string -func (m *FlagMeta) Shorts2String(sep ...string) string { - if len(m.Shorts) == 0 { - return "" - } - return strings.Join(m.Shorts, sepStr(sep)) -} - -// HelpName for show help -func (m *FlagMeta) HelpName() string { - return cflag.AddPrefixes(m.Name, m.Shorts) -} - -func (m *FlagMeta) helpNameLen() int { - return len(m.HelpName()) -} - -// Validate the binding value -func (m *FlagMeta) Validate(val string) error { - if m.Required && val == "" { - return fmt.Errorf("flag '%s' is required", m.Name) - } - - // call user custom validator - if m.Validator != nil { - return m.Validator(val) - } - return nil -} - -// Flag value -func (m *FlagMeta) Flag() *flag.Flag { - return m.flag -} - -// DValue wrap the default value -func (m *FlagMeta) DValue() *stdutil.Value { - if m.defVal == nil { - m.defVal = &stdutil.Value{V: m.DefVal} - } - return m.defVal -} diff --git a/gflag/opts.go b/gflag/opts.go new file mode 100644 index 0000000..98eff24 --- /dev/null +++ b/gflag/opts.go @@ -0,0 +1,138 @@ +package gflag + +import ( + "flag" + "fmt" + "strings" + + "github.com/gookit/gcli/v3/helper" + "github.com/gookit/goutil/cflag" + "github.com/gookit/goutil/stdutil" + "github.com/gookit/goutil/structs" + "github.com/gookit/goutil/strutil" +) + +/*********************************************************************** + * flag options metadata + ***********************************************************************/ + +// FlagMeta for an flag(option/argument) +type FlagMeta struct { + // go flag value + flag *flag.Flag + // Name of flag and description + Name, Desc string + // default value for the flag option + DefVal any + // wrapped the default value + defVal *structs.Value + // short names. eg: ["o", "a"] + Shorts []string + // EnvVar allow set flag value from ENV var + EnvVar string + + // --- advanced settings + + // Hidden the option on help + Hidden bool + // Required the option is required + Required bool + // Validator support validate the option flag value + Validator func(val string) error +} + +// newFlagMeta quick create an FlagMeta +func newFlagMeta(name, desc string, defVal any, shortcut string) *FlagMeta { + return &FlagMeta{ + Name: name, + Desc: desc, + // other info + DefVal: defVal, + Shorts: strings.Split(shortcut, ","), + } +} + +func (m *FlagMeta) initCheck() string { + if m.Desc != "" { + desc := strings.Trim(m.Desc, "; ") + if strings.ContainsRune(desc, ';') { + // format: desc;required + // format: desc;required;env TODO parse ENV var + parts := strutil.SplitNTrimmed(desc, ";", 2) + if ln := len(parts); ln > 1 { + bl, err := strutil.Bool(parts[1]) + if err == nil && bl { + desc = parts[0] + m.Required = true + } + } + } + + m.Desc = desc + } + + // filter shorts + if len(m.Shorts) > 0 { + m.Shorts = cflag.FilterNames(m.Shorts) + } + return m.goodName() +} + +// good name of the flag +func (m *FlagMeta) goodName() string { + name := strings.Trim(m.Name, "- ") + if name == "" { + helper.Panicf("option flag name cannot be empty") + } + + if !helper.IsGoodName(name) { + helper.Panicf("option flag name '%s' is invalid", name) + } + + // update self name + m.Name = name + return name +} + +// Shorts2String join shorts to a string +func (m *FlagMeta) Shorts2String(sep ...string) string { + if len(m.Shorts) == 0 { + return "" + } + return strings.Join(m.Shorts, sepStr(sep)) +} + +// HelpName for show help +func (m *FlagMeta) HelpName() string { + return cflag.AddPrefixes(m.Name, m.Shorts) +} + +func (m *FlagMeta) helpNameLen() int { + return len(m.HelpName()) +} + +// Validate the binding value +func (m *FlagMeta) Validate(val string) error { + if m.Required && val == "" { + return fmt.Errorf("flag '%s' is required", m.Name) + } + + // call user custom validator + if m.Validator != nil { + return m.Validator(val) + } + return nil +} + +// Flag value +func (m *FlagMeta) Flag() *flag.Flag { + return m.flag +} + +// DValue wrap the default value +func (m *FlagMeta) DValue() *stdutil.Value { + if m.defVal == nil { + m.defVal = &stdutil.Value{V: m.DefVal} + } + return m.defVal +}