diff --git a/app.go b/app.go index b088d5a..4d30ec3 100644 --- a/app.go +++ b/app.go @@ -73,6 +73,7 @@ func New(fns ...func(app *App)) *App { } // NewApp create new app instance. +// // Usage: // NewApp() // // Or with a config func @@ -101,7 +102,6 @@ func NewApp(fns ...func(app *App)) *App { Hooks: &Hooks{}, gFlags: NewFlags("app.GOptions").WithConfigFn(func(opt *FlagsConfig) { opt.WithoutType = true - opt.NameDescOL = true opt.Alignment = AlignLeft }), } diff --git a/cmd.go b/cmd.go index 240b92d..b8f5719 100644 --- a/cmd.go +++ b/cmd.go @@ -230,11 +230,9 @@ func (c *Command) Match(names []string) *Command { // ensure is initialized c.initialize() - ln := len(names) - if ln == 0 { // return self. + if len(names) == 0 { // return self. return c } - return c.commandBase.Match(names) } @@ -251,7 +249,6 @@ func (c *Command) initialize() { // check command name cName := c.goodName() - Debugf("initialize the command '%s'", cName) c.initialized = true @@ -272,13 +269,10 @@ func (c *Command) initialize() { // init for cmd Arguments c.Arguments.SetName(cName) - c.Arguments.SetValidateNum(gOpts.strictMode) + // c.Arguments.SetValidateNum(gOpts.strictMode) // init for cmd Flags - c.Flags.SetConfig(newDefaultFlagOption()) c.Flags.InitFlagSet(cName) - // c.Flags.SetOption(cName) - // c.Flags.FSet().SetOutput(c.Flags.out) // c.Flags.FSet().Usage = func() { // call on exists "-h" "--help" // Logf(VerbDebug, "render help on exists '-h|--help' or has unknown flag") // c.ShowHelp() @@ -367,8 +361,7 @@ var errCallRunOnSub = errors.New("c.Run() cannot allow call at subcommand") // cmd.MustRun([]string{"-a", ...}) func (c *Command) MustRun(args []string) { if err := c.Run(args); err != nil { - color.Error.Println("ERROR:", err.Error()) - panic(err) + color.Errorln("ERROR:", err.Error()) } } @@ -381,12 +374,8 @@ func (c *Command) MustRun(args []string) { // // custom args // cmd.Run([]string{"-a", ...}) func (c *Command) Run(args []string) (err error) { - if c.app != nil { - return errCallRunOnApp - } - - if c.parent != nil { - return errCallRunOnSub + if c.app != nil || c.parent != nil { + return c.innerDispatch(args) } // mark is standalone @@ -522,8 +511,6 @@ func (c *Command) parseOptions(args []string) (ss []string, err error) { // args = moveArgumentsToEnd(args) // Debugf("cmd: %s - option flags on after format: %v", c.Name, args) - // NOTICE: disable output internal error message on parse flags - // c.FSet().SetOutput(ioutil.Discard) Debugf("cmd: %s - will parse options by args: %v", c.Name, args) // parse options, don't contains command name. diff --git a/gargs.go b/gargs.go index 6a2fc86..975031e 100644 --- a/gargs.go +++ b/gargs.go @@ -1,10 +1,10 @@ package gcli import ( - "strconv" "strings" "github.com/gookit/goutil/errorx" + "github.com/gookit/goutil/structs" "github.com/gookit/goutil/strutil" ) @@ -12,14 +12,12 @@ import ( * Arguments definition *************************************************************/ -// an empty argument -var emptyArg = &Argument{} - // Arguments definition type Arguments struct { // Inherited from Command name string // args definition for a command. + // // eg. { // {"arg0", "this is first argument", false, false}, // {"arg1", "this is second argument", false, false}, @@ -28,6 +26,7 @@ type Arguments struct { // record min length for args // argsMinLen int // record argument names and defined positional relationships + // // { // // name: position // "arg0": 0, @@ -100,11 +99,9 @@ func (ags *Arguments) ParseArgs(args []string) (err error) { // Usage: // cmd.AddArg("name", "description") // cmd.AddArg("name", "description", true) // required -// cmd.AddArg("names", "description", true, true) // required and is array +// cmd.AddArg("names", "description", true, true) // required and is arrayed func (ags *Arguments) AddArg(name, desc string, requiredAndArrayed ...bool) *Argument { - // create new argument newArg := NewArgument(name, desc, requiredAndArrayed...) - return ags.AddArgument(newArg) } @@ -116,7 +113,7 @@ func (ags *Arguments) AddArgByRule(name, rule string) *Argument { newArg := NewArgument(name, mp["desc"], required) if defVal := mp["default"]; defVal != "" { - newArg.Value = defVal + newArg.Set(defVal) } return ags.AddArgument(newArg) @@ -200,17 +197,17 @@ func (ags *Arguments) HasArguments() bool { func (ags *Arguments) Arg(name string) *Argument { i, ok := ags.argsIndexes[name] if !ok { - return emptyArg + panicf("get not exists argument '%s'", name) } return ags.args[i] } // ArgByIndex get named arg by index func (ags *Arguments) ArgByIndex(i int) *Argument { - if i < len(ags.args) { - return ags.args[i] + if i >= len(ags.args) { + panicf("get not exists argument #%d", i) } - return emptyArg + return ags.args[i] } /************************************************************* @@ -219,31 +216,31 @@ func (ags *Arguments) ArgByIndex(i int) *Argument { // Argument a command argument definition type Argument struct { + *structs.Value // Name argument name. it's required Name string // Desc argument description message Desc string // Type name. eg: string, int, array // Type string + // ShowName is a name for display help. default is equals to Name. ShowName string // Required arg is required Required bool // Arrayed if is array, can allow accept multi values, and must in last. Arrayed bool - // value *goutil.Value TODO ... - // value store parsed argument data. (type: string, []string) - Value interface{} + // Handler custom argument value handler on call GetValue() - Handler func(val interface{}) interface{} - // Validator you can add an validator, will call it on binding argument value - Validator func(val interface{}) (interface{}, error) + Handler func(val any) any + // Validator you can add a validator, will call it on binding argument value + Validator func(val any) (any, error) // the argument position index in all arguments(cmd.args[index]) index int } -// NewArgument quick create an new command argument -func NewArgument(name, desc string, requiredAndArrayed ...bool) *Argument { +// NewArg quick create a new command argument +func NewArg(name, desc string, val any, requiredAndArrayed ...bool) *Argument { var arrayed, required bool if ln := len(requiredAndArrayed); ln > 0 { required = requiredAndArrayed[0] @@ -253,21 +250,52 @@ func NewArgument(name, desc string, requiredAndArrayed ...bool) *Argument { } return &Argument{ - Name: name, - Desc: desc, - // other options + Name: name, + Desc: desc, + Value: structs.NewValue(val), + // other settings // ShowName: name, Required: required, Arrayed: arrayed, } } +// NewArgument quick create a new command argument +func NewArgument(name, desc string, requiredAndArrayed ...bool) *Argument { + return NewArg(name, desc, nil, requiredAndArrayed...) +} + // SetArrayed the argument func (a *Argument) SetArrayed() *Argument { a.Arrayed = true return a } +// WithValue to the argument +func (a *Argument) WithValue(val any) *Argument { + a.Value.Set(val) + return a +} + +// WithFn a func for config the argument +func (a *Argument) WithFn(fn func(arg *Argument)) *Argument { + if fn != nil { + fn(a) + } + return a +} + +// WithValidator set a value validator of the argument +func (a *Argument) WithValidator(fn func(any) (any, error)) *Argument { + a.Validator = fn + return a +} + +// SetValue set an validated value +func (a *Argument) SetValue(val any) error { + return a.bindValue(val) +} + // Init the argument func (a *Argument) Init() *Argument { a.goodArgument() @@ -281,7 +309,7 @@ func (a *Argument) goodArgument() string { } if !goodName.MatchString(name) { - panicf("the command argument name '%s' is invalid, must match: %s", name, regGoodName) + panicf("the argument name '%s' is invalid, must match: %s", name, regGoodName) } a.Name = name @@ -291,34 +319,6 @@ func (a *Argument) goodArgument() string { return name } -// HelpName for render help message -func (a *Argument) HelpName() string { - if a.Arrayed { - return a.ShowName + "..." - } - return a.ShowName -} - -// With a func for config the argument -func (a *Argument) With(fn func(arg *Argument)) *Argument { - if fn != nil { - fn(a) - } - - return a -} - -// WithValidator set a value validator of the argument -func (a *Argument) WithValidator(fn func(interface{}) (interface{}, error)) *Argument { - a.Validator = fn - return a -} - -// SetValue set an validated value -func (a *Argument) SetValue(val interface{}) error { - return a.bindValue(val) -} - // GetValue get value by custom handler func func (a *Argument) GetValue() interface{} { val := a.Value @@ -329,62 +329,6 @@ func (a *Argument) GetValue() interface{} { return val } -// Int argument value to int -func (a *Argument) Int(defVal ...int) int { - def := 0 - if len(defVal) == 1 { - def = defVal[0] - } - - if a.Value == nil || a.Arrayed { - return def - } - - if intVal, ok := a.Value.(int); ok { - return intVal - } - - if str, ok := a.Value.(string); ok { - val, err := strconv.Atoi(str) - if err == nil { - return val - } - } - return def -} - -// String argument value to string -func (a *Argument) String(defVal ...string) string { - def := "" - if len(defVal) == 1 { - def = defVal[0] - } - - if a.Value == nil || a.Arrayed { - return def - } - - if str, ok := a.Value.(string); ok { - return str - } - return def -} - -// StringSplit quick split a string argument to string slice -func (a *Argument) StringSplit(sep ...string) (ss []string) { - str := a.String() - if str == "" { - return - } - - char := "," - if len(sep) > 0 { - char = sep[0] - } - - return strings.Split(str, char) -} - // Array alias of the Strings() func (a *Argument) Array() (ss []string) { return a.Strings() @@ -393,9 +337,8 @@ func (a *Argument) Array() (ss []string) { // Strings argument value to string array, if argument isArray = true. func (a *Argument) Strings() (ss []string) { if a.Value != nil && a.Arrayed { - ss = a.Value.([]string) + ss = a.Value.Strings() } - return } @@ -414,16 +357,27 @@ func (a *Argument) Index() int { return a.index } -// bind an value to the argument -func (a *Argument) bindValue(val interface{}) (err error) { - // has validator +// HelpName for render help message +func (a *Argument) HelpName() string { + if a.Arrayed { + return a.ShowName + "..." + } + return a.ShowName +} + +// bind a value to the argument +func (a *Argument) bindValue(val any) (err error) { if a.Validator != nil { val, err = a.Validator(val) - if err == nil { - a.Value = val + if err != nil { + return } - } else { - a.Value = val } + + if a.Handler != nil { + val = a.Handler(val) + } + + a.Value.V = val return } diff --git a/gargs_test.go b/gargs_test.go index 619a25e..ff2b21c 100644 --- a/gargs_test.go +++ b/gargs_test.go @@ -88,37 +88,36 @@ func TestArgument(t *testing.T) { // no value is.Nil(arg.Strings()) is.Nil(arg.GetValue()) - is.Nil(arg.StringSplit()) + is.Nil(arg.SplitToStrings()) is.Equal(0, arg.Int()) - is.Equal(34, arg.Int(34)) is.Equal("", arg.String()) - is.Equal("ab", arg.String("ab")) + is.Equal("ab", arg.WithValue("ab").String()) // add value - arg.Value = "ab,cd" + err := arg.SetValue("ab,cd") + is.NoError(err) is.Nil(arg.Strings()) is.Equal(0, arg.Int()) - is.Equal(34, arg.Int(34)) is.Equal("ab,cd", arg.String()) - is.Equal([]string{"ab", "cd"}, arg.StringSplit()) - is.Equal([]string{"ab", "cd"}, arg.StringSplit(",")) + is.Equal([]string{"ab", "cd"}, arg.Strings()) + is.Equal([]string{"ab", "cd"}, arg.SplitToStrings(",")) // int value - arg.Value = 23 + is.NoError(arg.SetValue(23)) is.Equal(23, arg.Int()) - is.Equal("", arg.String()) + is.Equal("23", arg.String()) // string int value - err := arg.SetValue("23") + err = arg.SetValue("23") is.NoError(err) is.Equal(23, arg.Int()) is.Equal("23", arg.String()) // array value arg.Arrayed = true - arg.Value = []string{"a", "b"} + is.NoError(arg.SetValue([]string{"a", "b"})) is.True(arg.Arrayed) is.Equal(0, arg.Int()) is.Equal("", arg.String()) @@ -136,7 +135,7 @@ func TestArgument_GetValue(t *testing.T) { arg := gcli.NewArgument("arg0", "arg desc") // custom handler - arg.Value = "a-b-c" + assert.NoError(t, arg.SetValue("a-b-c")) arg.Handler = func(value interface{}) interface{} { str := value.(string) return strings.SplitN(str, "-", 2) @@ -149,8 +148,8 @@ var str2int = func(val interface{}) (interface{}, error) { } func TestArgument_WithConfig(t *testing.T) { - arg := gcli.NewArgument("arg0", "arg desc").With(func(arg *gcli.Argument) { - arg.Value = 23 + arg := gcli.NewArgument("arg0", "arg desc").WithFn(func(arg *gcli.Argument) { + arg.SetValue(23) arg.Init() }) diff --git a/gflag.go b/gflag.go index 400ddcd..ac88a29 100644 --- a/gflag.go +++ b/gflag.go @@ -13,10 +13,10 @@ import ( "unsafe" "github.com/gookit/color" - "github.com/gookit/goutil" "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" ) @@ -48,9 +48,9 @@ var FlagTagName = "flag" type FlagsConfig struct { // WithoutType don't display flag data type on print help WithoutType bool - // NameDescOL flag and desc at one line on print help - NameDescOL bool - // Alignment flag name align left or right. default is: right + // DescNewline flag desc at new line on print help + DescNewline bool + // Alignment flag name align left or right. default is: left Alignment uint8 // TagName on struct TagName string @@ -58,29 +58,37 @@ type FlagsConfig struct { TagRuleType uint8 } +// OptCategory struct +type OptCategory struct { + Name, Title string + OptNames []string +} + // Flags struct definition type Flags struct { // Desc message Desc string // ExitFunc for handle exit ExitFunc func(code int) - // FlagsConfig option for render help message + + // cfg option for the flags cfg *FlagsConfig - // raw flag set + // 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 for options. format: {short:name} + // short names map for options. format: {short: name} // eg. {"n": "name", "o": "opt"} shorts map[string]string - // mapping for name to shortcut {"name": {"n", "m"}} - // name2shorts map[string][]string + // support option category + categories []OptCategory // flag name max length. useful for render help // eg: "-V, --version" length is 13 flagMaxLen int @@ -88,18 +96,18 @@ type Flags struct { existShort bool } -func newDefaultFlagOption() *FlagsConfig { +func newDefaultFlagConfig() *FlagsConfig { return &FlagsConfig{ - Alignment: AlignRight, + Alignment: AlignLeft, TagName: FlagTagName, } } -// NewFlags create an new Flags +// NewFlags create a new Flags func NewFlags(nameWithDesc ...string) *Flags { fs := &Flags{ out: os.Stdout, - cfg: newDefaultFlagOption(), + cfg: newDefaultFlagConfig(), } fs.ExitFunc = os.Exit @@ -121,6 +129,10 @@ func (fs *Flags) InitFlagSet(name string) { 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(ioutil.Discard) @@ -138,13 +150,104 @@ func (fs *Flags) UseSimpleRule() *Flags { } // WithConfigFn for the object. -func (fs *Flags) WithConfigFn(fns ...func(opt *FlagsConfig)) *Flags { +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 := gcli.NewFlags() +// ... +// 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 { + color.Errorf("Parse error: %s\n", err.Error()) + } +} + +// Parse given arguments +// +// Usage: +// gf := gcli.NewFlags() +// 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 { + // if Debug { + // cliutil.Errorln("recover error on run parse") + // } + color.Errorln("Panic 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) + } + + // do parsing + if err = fs.fSet.Parse(args); err != nil { + if err == flag.ErrHelp { + return nil // ignore help error + } + 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 { + // disable output internal error message on parse flags + fs.fSet.SetOutput(ioutil.Discard) + + return nil +} + /*********************************************************************** * Flags: * - binding option from struct @@ -269,80 +372,6 @@ func (fs *Flags) FromStruct(ptr interface{}) error { return nil } -// Run flags parse and handle help render -// -// Usage: -// gf := gcli.NewFlags() -// ... -// 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 { - fs.ExitFunc(0) - } else { - color.Errorf("flag parse error - %s", err.Error()) - fs.ExitFunc(2) - } - } -} - -// Parse given arguments -// -// Usage: -// gf := gcli.NewFlags() -// 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) { - err = fs.fSet.Parse(args) - if err != nil { - return - } - - // 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 -} - -// 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: * - binding option var @@ -351,17 +380,14 @@ func (fs *Flags) FSetArgs() []string { return fs.fSet.Args() } // --- bool option // Bool binding a bool option flag, return pointer -func (fs *Flags) Bool(name, shorts string, defValue bool, desc string) *bool { - meta := newFlagMeta(name, desc, defValue, shorts) +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, defValue, meta.Desc) + p := fs.fSet.Bool(name, defVal, meta.Desc) + meta.Flag = fs.fSet.Lookup(name) - // binding all short name options to flag.FlagSet - for _, s := range meta.Shorts { - fs.fSet.BoolVar(p, s, defValue, "") // don't add description for short name - } return p } @@ -369,22 +395,18 @@ func (fs *Flags) Bool(name, shorts string, defValue bool, desc string) *bool { 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, defValue bool, desc string) { - fs.boolOpt(p, newFlagMeta(name, desc, defValue, shorts)) +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() - fmtName := fs.checkFlagInfo(meta) + name := fs.checkFlagInfo(meta) // binding option to flag.FlagSet - fs.fSet.BoolVar(p, fmtName, defVal, meta.Desc) - - // binding all short name options to flag.FlagSet - for _, s := range meta.Shorts { - fs.fSet.BoolVar(p, s, defVal, "") // dont add description for short name - } + fs.fSet.BoolVar(p, name, defVal, meta.Desc) + meta.Flag = fs.fSet.Lookup(name) } // --- float option @@ -393,21 +415,17 @@ func (fs *Flags) boolOpt(p *bool, meta *FlagMeta) { 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, defValue float64, desc string) { - fs.float64Opt(p, newFlagMeta(name, desc, defValue, shorts)) +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) { - defValue := meta.DValue().Float64() - fmtName := fs.checkFlagInfo(meta) + defVal := meta.DValue().Float64() + name := fs.checkFlagInfo(meta) // binding option to flag.FlagSet - fs.fSet.Float64Var(p, fmtName, defValue, meta.Desc) - - // binding all short name options to flag.FlagSet - for _, s := range meta.Shorts { - fs.fSet.Float64Var(p, s, defValue, "") // dont add description for short name - } + fs.fSet.Float64Var(p, name, defVal, meta.Desc) + meta.Flag = fs.fSet.Lookup(name) } // --- string option @@ -419,11 +437,8 @@ func (fs *Flags) Str(name, shorts string, defValue, desc string) *string { // binding option to flag.FlagSet p := fs.fSet.String(name, defValue, meta.Desc) + meta.Flag = fs.fSet.Lookup(name) - // binding all short name options to flag.FlagSet - for _, s := range meta.Shorts { - fs.fSet.StringVar(p, s, defValue, "") // dont add description for short name - } return p } @@ -437,11 +452,12 @@ func (fs *Flags) StrOpt(p *string, name, shorts, defValue, desc string) { // binding option and shorts func (fs *Flags) strOpt(p *string, meta *FlagMeta) { - defValue := meta.DValue().String() - fs.checkFlagInfo(meta) + defVal := meta.DValue().String() + name := fs.checkFlagInfo(meta) // binding option to flag.FlagSet - fs.fSet.StringVar(p, meta.Name, defValue, meta.Desc) + fs.fSet.StringVar(p, meta.Name, defVal, meta.Desc) + meta.Flag = fs.fSet.Lookup(name) } // --- intX option @@ -453,11 +469,8 @@ func (fs *Flags) Int(name, shorts string, defValue int, desc string) *int { // binding option to flag.FlagSet p := fs.fSet.Int(name, defValue, meta.Desc) + meta.Flag = fs.fSet.Lookup(name) - // binding all short name options to flag.FlagSet - for _, s := range meta.Shorts { - fs.fSet.IntVar(p, s, defValue, "") // dont add description for short name - } return p } @@ -471,15 +484,11 @@ func (fs *Flags) IntOpt(p *int, name, shorts string, defValue int, desc string) func (fs *Flags) intOpt(p *int, meta *FlagMeta) { defValue := meta.DValue().Int() - fmtName := fs.checkFlagInfo(meta) + name := fs.checkFlagInfo(meta) // binding option to flag.FlagSet - fs.fSet.IntVar(p, fmtName, defValue, meta.Desc) - - // binding all short name options to flag.FlagSet - for _, s := range meta.Shorts { - fs.fSet.IntVar(p, s, defValue, "") // dont add description for short name - } + fs.fSet.IntVar(p, name, defValue, meta.Desc) + meta.Flag = fs.fSet.Lookup(name) } // Int64 binding an int64 option flag, return pointer @@ -489,11 +498,8 @@ func (fs *Flags) Int64(name, shorts string, defValue int64, desc string) *int64 // binding option to flag.FlagSet p := fs.fSet.Int64(name, defValue, meta.Desc) + meta.Flag = fs.fSet.Lookup(name) - // binding all short name options to flag.FlagSet - for _, s := range meta.Shorts { - fs.fSet.Int64Var(p, s, defValue, "") // dont add description for short name - } return p } @@ -506,32 +512,25 @@ func (fs *Flags) Int64Opt(p *int64, name, shorts string, defValue int64, desc st } func (fs *Flags) int64Opt(p *int64, meta *FlagMeta) { - defValue := meta.DValue().Int64() - fmtName := fs.checkFlagInfo(meta) + defVal := meta.DValue().Int64() + name := fs.checkFlagInfo(meta) // binding option to flag.FlagSet - fs.fSet.Int64Var(p, fmtName, defValue, meta.Desc) - - // binding all short options to flag.FlagSet - for _, s := range meta.Shorts { - fs.fSet.Int64Var(p, s, defValue, "") // dont add description for short name - } + 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, defValue uint, desc string) *uint { - meta := newFlagMeta(name, desc, defValue, shorts) +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, defValue, meta.Desc) + p := fs.fSet.Uint(name, defVal, meta.Desc) + meta.Flag = fs.fSet.Lookup(name) - // binding all short name options to flag.FlagSet - for _, s := range meta.Shorts { - fs.fSet.UintVar(p, s, defValue, "") // dont add description for short name - } return p } @@ -544,30 +543,22 @@ func (fs *Flags) UintOpt(p *uint, name, shorts string, defValue uint, desc strin } func (fs *Flags) uintOpt(p *uint, meta *FlagMeta) { - defValue := meta.DValue().Int() - fmtName := fs.checkFlagInfo(meta) + defVal := meta.DValue().Int() + name := fs.checkFlagInfo(meta) // binding option to flag.FlagSet - fs.fSet.UintVar(p, fmtName, uint(defValue), meta.Desc) - - // binding all short options to flag.FlagSet - for _, s := range meta.Shorts { - fs.fSet.UintVar(p, s, uint(defValue), "") // dont add description for short name - } + 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, defValue uint64, desc string) *uint64 { - meta := newFlagMeta(name, desc, defValue, shorts) +func (fs *Flags) Uint64(name, shorts string, defVal uint64, desc string) *uint64 { + meta := newFlagMeta(name, desc, defVal, shorts) name = fs.checkFlagInfo(meta) - // binding option to flag.FlagSet - p := fs.fSet.Uint64(name, defValue, meta.Desc) + p := fs.fSet.Uint64(name, defVal, meta.Desc) + meta.Flag = fs.fSet.Lookup(name) - // binding all short name options to flag.FlagSet - for _, s := range meta.Shorts { - fs.fSet.Uint64Var(p, s, defValue, "") // dont add description for short name - } return p } @@ -575,22 +566,18 @@ func (fs *Flags) Uint64(name, shorts string, defValue uint64, desc string) *uint 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, defValue uint64, desc string) { - fs.uint64Opt(p, newFlagMeta(name, desc, defValue, shorts)) +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) { - defValue := meta.DValue().Int64() - fmtName := fs.checkFlagInfo(meta) + defVal := meta.DValue().Int64() + name := fs.checkFlagInfo(meta) // binding option to flag.FlagSet - fs.fSet.Uint64Var(p, fmtName, uint64(defValue), meta.Desc) - - // binding all short options to flag.FlagSet - for _, s := range meta.Shorts { - fs.fSet.Uint64Var(p, s, uint64(defValue), "") // dont add description for short name - } + fs.fSet.Uint64Var(p, name, uint64(defVal), meta.Desc) + meta.Flag = fs.fSet.Lookup(name) } // Var binding an custom var option flag @@ -607,15 +594,11 @@ func (fs *Flags) VarOpt(p flag.Value, name, shorts, desc string) { // binding option and shorts func (fs *Flags) varOpt(p flag.Value, meta *FlagMeta) { - fmtName := fs.checkFlagInfo(meta) + name := fs.checkFlagInfo(meta) // binding option to flag.FlagSet - fs.fSet.Var(p, fmtName, meta.Desc) - - // binding all short options to flag.FlagSet - for _, s := range meta.Shorts { - fs.fSet.Var(p, s, "") // dont add description for short name - } + fs.fSet.Var(p, name, meta.Desc) + meta.Flag = fs.fSet.Lookup(name) } // Required flag option name(s) @@ -625,7 +608,6 @@ func (fs *Flags) Required(names ...string) { if !ok { panicf("undefined option flag '%s'", name) } - meta.Required = true } } @@ -645,36 +627,30 @@ func (fs *Flags) checkFlagInfo(meta *FlagMeta) string { panicf("redefined option flag '%s'", name) } - nameLength := len(name) // is a short name - if nameLength == 1 { - nameLength += 1 // prefix: "-" - fs.existShort = true - } else { - nameLength += 2 // prefix: "--" - } - + helpLen := meta.helpNameLen() // fix: must exclude Hidden option - if !meta.Hidden && fs.flagMaxLen < nameLength { - fs.flagMaxLen = nameLength + if !meta.Hidden { + fs.flagMaxLen = mathutil.MaxInt(fs.flagMaxLen, helpLen) } // check short names - fs.checkShortNames(name, nameLength, meta.Shorts) + 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, nameLength int, shorts []string) { +func (fs *Flags) checkShortNames(name string, shorts []string) { if len(shorts) == 0 { - fs.names[name] = nameLength return } - // init fs.shorts and fs.name2shorts + fs.existShort = true if fs.shorts == nil { fs.shorts = map[string]string{} } @@ -696,17 +672,6 @@ func (fs *Flags) checkShortNames(name string, nameLength int, shorts []string) { fs.shorts[short] = name } - // one short = '-' + 'x' + ',' + ' ' - // eg: "-o, " len=4 - // eg: "-o, -a, " len=8 - nameLength += 4 * len(shorts) - fs.existShort = true - - // update name length - fs.names[name] = nameLength - fs.flagMaxLen = mathutil.MaxInt(fs.flagMaxLen, nameLength) - - // fs.name2shorts[name] = fmtShorts } /*********************************************************************** @@ -732,11 +697,6 @@ func (fs *Flags) String() string { // repeat call the method if fs.buf.Len() < 1 { - // if fs.existShort { // add 4 space prefix for flag - // fs.flagMaxLen += 4 - // } - - // refer fs.Fs().PrintDefaults() fs.FSet().VisitAll(fs.formatOneFlag) } @@ -756,33 +716,38 @@ func (fs *Flags) formatOneFlag(f *flag.Flag) { var s, fullName string name := f.Name // eg: "-V, --version" length is: 13 - fLen := fs.names[name] + nameLen := fs.names[name] + descNl := fs.cfg.DescNewline // add prefix '-' to option fullName = cflag.AddPrefixes(name, meta.Shorts) - // fs.NameDescOL = true: padding space to same width. - if fs.cfg.NameDescOL { - fullName = strutil.Padding(fullName, " ", fs.flagMaxLen, fs.cfg.Alignment) - } - 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 == false && len(typeName) > 0 { + if !fs.cfg.WithoutType && len(typeName) > 0 { + typeLen := len(typeName) + 1 + if nameLen+typeLen > fs.flagMaxLen { + descNl = true + } else { + nameLen += typeLen + } + s += fmt.Sprintf(" %s", typeName) } - // - flag and description at one line - // - Boolean flags of one ASCII letter are so common we - // treat them specially, putting their usage on the same line. - if fs.cfg.NameDescOL || (typeName == "" && fLen <= 4) { // space, space, '-', 'x'. - s += " " + // display description on new line + nlIndent := "\n " + strings.Repeat(" ", fs.flagMaxLen) + if descNl { + s += nlIndent } else { - // display description on new line - s += "\n " + // padding space to flagMaxLen width. + if padLen := fs.flagMaxLen - nameLen; padLen > 0 { + s += strings.Repeat(" ", padLen) + } + s += " " } // --- build description @@ -795,7 +760,7 @@ func (fs *Flags) formatOneFlag(f *flag.Flag) { s += "*" } - s += strings.Replace(strutil.UpperFirst(desc), "\n", "\n ", -1) + s += strings.Replace(strutil.UpperFirst(desc), "\n", nlIndent, -1) // ---- append default value if isZero, isStr := cflag.IsZeroValue(f, f.DefValue); !isZero { @@ -905,6 +870,17 @@ 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 @@ -914,21 +890,20 @@ func (fs *Flags) FlagNames() map[string]int { return fs.names } type FlagMeta struct { // Flag value Flag *flag.Flag - // varPtr interface{} - // name and description + // Name of flag and description Name, Desc string - // Alias of the name. isn't shorts. eg: name='dry-run' alias='dr' TODO - Alias string // default value for the flag option DefVal interface{} // wrapped the default value - defVal *goutil.Value + defVal *structs.Value // short names. eg: ["o", "a"] Shorts []string - // advanced settings - // hidden the option on help + + // --- advanced settings + + // Hidden the option on help Hidden bool - // the option is required + // Required the option is required Required bool // Validator support validate the option flag value Validator func(val string) error @@ -958,6 +933,15 @@ func (m *FlagMeta) Shorts2String(sep ...string) string { return strings.Join(m.Shorts, char) } +// 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 == "" { diff --git a/go.mod b/go.mod index 9558526..02459f4 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gookit/gcli/v3 -go 1.16 +go 1.18 require ( github.com/gookit/color v1.5.1 @@ -8,3 +8,13 @@ require ( github.com/stretchr/testify v1.8.0 golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 ) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect + golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect + golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +)