diff --git a/gcli.go b/gcli.go index 725e34a..6a8d538 100644 --- a/gcli.go +++ b/gcli.go @@ -244,40 +244,23 @@ func IsDebugMode() bool { return gOpts.Verbose >= VerbDebug } *************************************************************************/ // Ints The int flag list, implemented flag.Value interface +// Deprecated type Ints = cflag.Ints // Strings The string flag list, implemented flag.Value interface +// Deprecated type Strings = cflag.Strings // Booleans The bool flag list, implemented flag.Value interface +// Deprecated type Booleans = cflag.Booleans // EnumString The string flag list, implemented flag.Value interface +// Deprecated type EnumString = cflag.EnumString -// KVString The key-value string flag, repeatable. -type KVString = cflag.KVString - -// ConfString The config-string flag, INI format, like nginx-config -type ConfString = cflag.ConfString - // String type, a special string -// -// Usage: -// -// // case 1: -// var names gcli.String -// c.VarOpt(&names, "names", "", "multi name by comma split") -// -// --names "tom,john,joy" -// names.Split(",") -> []string{"tom","john","joy"} -// -// // case 2: -// var ids gcli.String -// c.VarOpt(&ids, "ids", "", "multi id by comma split") -// -// --names "23,34,56" -// names.Ints(",") -> []int{23,34,56} +// Deprecated type String = cflag.String /************************************************************************* diff --git a/gflag/gflag.go b/gflag/gflag.go index 0ed39b3..c4d0bef 100644 --- a/gflag/gflag.go +++ b/gflag/gflag.go @@ -55,6 +55,12 @@ type OptCategory struct { // Ints The int flag list, implemented flag.Value interface type Ints = cflag.Ints +// IntsString implemented flag.Value interface +type IntsString = cflag.IntsString + +// String The special string flag, implemented flag.Value interface +type String = cflag.String + // Strings The string flag list, implemented flag.Value interface type Strings = cflag.Strings diff --git a/gflag/help.go b/gflag/help.go new file mode 100644 index 0000000..6fc802d --- /dev/null +++ b/gflag/help.go @@ -0,0 +1,146 @@ +package gflag + +import ( + "bytes" + "flag" + "fmt" + "strings" + + "github.com/gookit/color" + "github.com/gookit/goutil/cflag" + "github.com/gookit/goutil/strutil" +) + +/*********************************************************************** + * Flags: + * - render help message + ***********************************************************************/ + +// SetHelpRender set the raw *flag.FlagSet.Usage +func (p *Parser) SetHelpRender(fn func()) { + p.fSet.Usage = fn +} + +// PrintHelpPanel for all options to the gf.out +func (p *Parser) PrintHelpPanel() { + color.Fprint(p.out, p.String()) +} + +// String for all flag options +func (p *Parser) String() string { + return p.BuildHelp() +} + +// BuildHelp string for all flag options +func (p *Parser) BuildHelp() string { + if p.buf == nil { + p.buf = new(bytes.Buffer) + } + + // repeat call the method + if p.buf.Len() < 1 { + p.buf.WriteString("Options:\n") + p.buf.WriteString(p.BuildOptsHelp()) + p.buf.WriteByte('\n') + + if p.HasArgs() { + p.buf.WriteString("Arguments:\n") + p.buf.WriteString(p.BuildArgsHelp()) + p.buf.WriteByte('\n') + } + } + + return p.buf.String() +} + +// BuildOptsHelp string. +func (p *Parser) BuildOptsHelp() string { + var sb strings.Builder + + p.fSet.VisitAll(func(f *flag.Flag) { + line := p.formatOneFlag(f) + if line != "" { + sb.WriteString(line) + sb.WriteByte('\n') + } + }) + + return sb.String() +} + +func (p *Parser) formatOneFlag(f *flag.Flag) (s string) { + // Skip render: + // - opt is not exists(Has ensured that it is not a short name) + // - it is hidden flag option + // - flag desc is empty + opt, has := p.opts[f.Name] + if !has || opt.Hidden { + return + } + + var fullName string + name := f.Name + // eg: "-V, --version" length is: 13 + nameLen := p.names[name] + // display description on new line + descNl := p.cfg.DescNewline + + var nlIndent string + if descNl { + nlIndent = "\n " + } else { + nlIndent = "\n " + strings.Repeat(" ", p.optMaxLen) + } + + // add prefix '-' to option + fullName = cflag.AddPrefixes2(name, opt.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 !p.cfg.WithoutType && len(typeName) > 0 { + typeLen := len(typeName) + 1 + if !descNl && nameLen+typeLen > p.optMaxLen { + descNl = true + } else { + nameLen += typeLen + } + + s += fmt.Sprintf(" %s", typeName) + } + + if descNl { + s += nlIndent + } else { + // padding space to optMaxLen width. + if padLen := p.optMaxLen - nameLen; padLen > 0 { + s += strings.Repeat(" ", padLen) + } + s += " " + } + + // --- build description + if desc == "" { + desc = defaultDesc + } else { + desc = strings.Replace(strutil.UpperFirst(desc), "\n", nlIndent, -1) + } + + s += getRequiredMark(opt.Required) + desc + + // ---- 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) + } + } + + // arrayed, repeatable + if _, ok := f.Value.(cflag.RepeatableFlag); ok { + s += " (repeatable)" + } + return s +} diff --git a/gflag/opts.go b/gflag/opts.go index f2808e1..dae5f61 100644 --- a/gflag/opts.go +++ b/gflag/opts.go @@ -318,6 +318,11 @@ func (ops *CliOpts) VarOpt(v flag.Value, name, shorts, desc string) { ops.varOpt(v, newOpt(name, desc, nil, shorts)) } +// VarOpt2 binding an int option and with config func. +func (ops *CliOpts) VarOpt2(v flag.Value, nameAndShorts, desc string, setFns ...CliOptFn) { + ops.varOpt(v, NewOpt(nameAndShorts, desc, nil, setFns...)) +} + // binding option and shorts func (ops *CliOpts) varOpt(v flag.Value, opt *CliOpt) { name := ops.checkFlagInfo(opt) diff --git a/gflag/parser.go b/gflag/parser.go index b64f293..e003051 100644 --- a/gflag/parser.go +++ b/gflag/parser.go @@ -8,7 +8,6 @@ import ( "io" "os" "reflect" - "strings" "unsafe" "github.com/gookit/color" @@ -235,6 +234,27 @@ var ( ) // FromStruct from struct tag binding options +// +// ## Named rule: +// +// // tag format: name=val0;shorts=i;required=true;desc=a message +// type UserCmdOpts struct { +// Name string `flag:"name=name;shorts=n;required=true;desc=input username"` +// Age int `flag:"name=age;shorts=a;required=true;desc=input user age"` +// } +// opt := &UserCmdOpts{} +// p.FromStruct(opt) +// +// ## Simple rule +// +// // tag format1: name;desc;required;default;shorts +// // tag format2: desc;required;default;shorts +// type UserCmdOpts struct { +// Name string `flag:"input username;true;;n"` +// Age int `flag:"age;input user age;true;;o"` +// } +// opt := &UserCmdOpts{} +// p.UseSimpleRule().FromStruct(opt) func (p *Parser) FromStruct(ptr any) (err error) { v := reflect.ValueOf(ptr) if v.Kind() != reflect.Ptr { @@ -362,136 +382,6 @@ func (p *Parser) Required(names ...string) { } } -/*********************************************************************** - * Flags: - * - render help message - ***********************************************************************/ - -// SetHelpRender set the raw *flag.FlagSet.Usage -func (p *Parser) SetHelpRender(fn func()) { - p.fSet.Usage = fn -} - -// PrintHelpPanel for all options to the gf.out -func (p *Parser) PrintHelpPanel() { - color.Fprint(p.out, p.String()) -} - -// String for all flag options -func (p *Parser) String() string { - return p.BuildHelp() -} - -// BuildHelp string for all flag options -func (p *Parser) BuildHelp() string { - if p.buf == nil { - p.buf = new(bytes.Buffer) - } - - // repeat call the method - if p.buf.Len() < 1 { - p.buf.WriteString("Options:\n") - p.buf.WriteString(p.BuildOptsHelp()) - p.buf.WriteByte('\n') - - if p.HasArgs() { - p.buf.WriteString("Arguments:\n") - p.buf.WriteString(p.BuildArgsHelp()) - p.buf.WriteByte('\n') - } - } - - return p.buf.String() -} - -// BuildOptsHelp string. -func (p *Parser) BuildOptsHelp() string { - var sb strings.Builder - - p.fSet.VisitAll(func(f *flag.Flag) { - line := p.formatOneFlag(f) - if line != "" { - sb.WriteString(line) - sb.WriteByte('\n') - } - }) - - return sb.String() -} - -func (p *Parser) formatOneFlag(f *flag.Flag) (s string) { - // Skip render: - // - opt is not exists(Has ensured that it is not a short name) - // - it is hidden flag option - // - flag desc is empty - opt, has := p.opts[f.Name] - if !has || opt.Hidden { - return - } - - var fullName string - name := f.Name - // eg: "-V, --version" length is: 13 - nameLen := p.names[name] - // display description on new line - descNl := p.cfg.DescNewline - - var nlIndent string - if descNl { - nlIndent = "\n " - } else { - nlIndent = "\n " + strings.Repeat(" ", p.optMaxLen) - } - - // add prefix '-' to option - fullName = cflag.AddPrefixes2(name, opt.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 !p.cfg.WithoutType && len(typeName) > 0 { - typeLen := len(typeName) + 1 - if !descNl && nameLen+typeLen > p.optMaxLen { - descNl = true - } else { - nameLen += typeLen - } - - s += fmt.Sprintf(" %s", typeName) - } - - if descNl { - s += nlIndent - } else { - // padding space to optMaxLen width. - if padLen := p.optMaxLen - nameLen; padLen > 0 { - s += strings.Repeat(" ", padLen) - } - s += " " - } - - // --- build description - if desc == "" { - desc = defaultDesc - } else { - desc = strings.Replace(strutil.UpperFirst(desc), "\n", nlIndent, -1) - } - - s += getRequiredMark(opt.Required) + desc - - // ---- 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) - } - } - - return s -} - /*********************************************************************** * Flags: * - helper methods