From abb7a1c5b3a3c91f2738c96870e059212067e3a4 Mon Sep 17 00:00:00 2001 From: Jason Kingsbury Date: Thu, 13 Oct 2016 13:51:16 +0100 Subject: [PATCH 1/8] add support for non-nil interface composition --- group.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/group.go b/group.go index b1ef6ed..3f98fe1 100644 --- a/group.go +++ b/group.go @@ -212,6 +212,21 @@ func (g *Group) scanStruct(realval reflect.Value, sfield *reflect.StructField, h if err := g.scanStruct(fld, &field, handler); err != nil { return err } + } else if kind == reflect.Interface { + if fld.IsNil() { + continue + } + v := reflect.ValueOf(fld.Interface()) + switch v.Kind() { + case reflect.Struct: + if err := g.scanStruct(v, &field, handler); err != nil { + return err + } + case reflect.Ptr: + if err := g.scanStruct(reflect.Indirect(v), &field, handler); err != nil { + return err + } + } } else if kind == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct { if fld.IsNil() { fld.Set(reflect.New(fld.Type().Elem())) From 5b3038b6861911d30eacd40766b8c5135404fb7a Mon Sep 17 00:00:00 2001 From: Jason Kingsbury Date: Thu, 13 Oct 2016 13:53:21 +0100 Subject: [PATCH 2/8] explicit flag set on interface struct field --- group.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/group.go b/group.go index 3f98fe1..a9ee196 100644 --- a/group.go +++ b/group.go @@ -213,7 +213,7 @@ func (g *Group) scanStruct(realval reflect.Value, sfield *reflect.StructField, h return err } } else if kind == reflect.Interface { - if fld.IsNil() { + if fld.IsNil() || mtag.Get("flag") == "" { continue } v := reflect.ValueOf(fld.Interface()) From 56e1bdbb797d3a52ff2ac3f7ae281acddadfba5a Mon Sep 17 00:00:00 2001 From: Jason Kingsbury Date: Thu, 13 Oct 2016 14:10:51 +0100 Subject: [PATCH 3/8] added examples --- example_test.go | 11 +++++++++++ flags.go | 1 - group.go | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/example_test.go b/example_test.go index 4321ed8..01abc88 100644 --- a/example_test.go +++ b/example_test.go @@ -39,6 +39,9 @@ func Example() { // Example of a filename (useful for completion) Filename Filename `long:"filename" description:"A filename"` + // Example of an interface + Interface interface{} + // Example of positional arguments Args struct { ID string @@ -56,6 +59,11 @@ func Example() { cmd.Process.Release() } + // Set the value of interface + opts.Interface = struct { + Sub string `long:"sub-field" description:"An embedded interface field"` + }{} + // Make some fake arguments to parse. args := []string{ "-vv", @@ -69,6 +77,7 @@ func Example() { "--intmap", "a:1", "--intmap", "b:5", "--filename", "hello.go", + "--sub-field", "hello", "id", "10", "remaining1", @@ -92,6 +101,7 @@ func Example() { fmt.Printf("PtrSlice: [%v %v]\n", *opts.PtrSlice[0], *opts.PtrSlice[1]) fmt.Printf("IntMap: [a:%v b:%v]\n", opts.IntMap["a"], opts.IntMap["b"]) fmt.Printf("Filename: %v\n", opts.Filename) + fmt.Printf("Interface: %+v\n", opts.Interface) fmt.Printf("Args.ID: %s\n", opts.Args.ID) fmt.Printf("Args.Num: %d\n", opts.Args.Num) fmt.Printf("Args.Rest: %v\n", opts.Args.Rest) @@ -104,6 +114,7 @@ func Example() { // PtrSlice: [hello world] // IntMap: [a:1 b:5] // Filename: hello.go + // Interface: {Sub:hello} // Args.ID: id // Args.Num: 10 // Args.Rest: [remaining1 remaining2] diff --git a/flags.go b/flags.go index 9c815e0..30923f2 100644 --- a/flags.go +++ b/flags.go @@ -85,7 +85,6 @@ The following is a list of tags for struct fields supported by go-flags: long-description: the long description of the option. Currently only displayed in generated man pages (optional) no-flag: if non-empty this field is ignored as an option (optional) - optional: whether an argument of the option is optional. When an argument is optional it can only be specified using --option=argument (optional) diff --git a/group.go b/group.go index a9ee196..3f98fe1 100644 --- a/group.go +++ b/group.go @@ -213,7 +213,7 @@ func (g *Group) scanStruct(realval reflect.Value, sfield *reflect.StructField, h return err } } else if kind == reflect.Interface { - if fld.IsNil() || mtag.Get("flag") == "" { + if fld.IsNil() { continue } v := reflect.ValueOf(fld.Interface()) From b64bb008e91d421fb0f6ae768cb53b278cf7f1f7 Mon Sep 17 00:00:00 2001 From: Jason Kingsbury Date: Mon, 6 Feb 2017 11:23:25 +0000 Subject: [PATCH 4/8] expose completion method --- completion.go | 2 +- parser.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/completion.go b/completion.go index 708fa9e..1fc75f2 100644 --- a/completion.go +++ b/completion.go @@ -123,8 +123,8 @@ func (c *completion) completeValue(value reflect.Value, prefix string, match str i := value.Interface() var ret []Completion - if cmp, ok := i.(Completer); ok { + ret = cmp.Complete(match) } else if value.CanAddr() { if cmp, ok = value.Addr().Interface().(Completer); ok { diff --git a/parser.go b/parser.go index 3a55626..e33c145 100644 --- a/parser.go +++ b/parser.go @@ -185,6 +185,11 @@ func (p *Parser) Parse() ([]string, error) { return p.ParseArgs(os.Args[1:]) } +func (p *Parser) Complete(match []string) []Completion { + comp := &completion{parser: p} + return comp.complete(match) +} + // ParseArgs parses the command line arguments according to the option groups that // were added to the parser. On successful parsing of the arguments, the // remaining, non-option, arguments (if any) are returned. The returned error From 206d9f91e9e663c75fc8ce9275e966f8db4dd93b Mon Sep 17 00:00:00 2001 From: Jason Kingsbury Date: Mon, 6 Feb 2017 11:34:44 +0000 Subject: [PATCH 5/8] remove interface example --- example_test.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/example_test.go b/example_test.go index 01abc88..33764e5 100644 --- a/example_test.go +++ b/example_test.go @@ -39,8 +39,8 @@ func Example() { // Example of a filename (useful for completion) Filename Filename `long:"filename" description:"A filename"` - // Example of an interface - Interface interface{} + //// Example of an interface + //Interface interface{} // Example of positional arguments Args struct { @@ -59,10 +59,10 @@ func Example() { cmd.Process.Release() } - // Set the value of interface - opts.Interface = struct { - Sub string `long:"sub-field" description:"An embedded interface field"` - }{} + //// Set the value of interface + //opts.Interface = struct { + // Sub string `long:"sub-field" description:"An embedded interface field"` + //}{} // Make some fake arguments to parse. args := []string{ @@ -77,7 +77,7 @@ func Example() { "--intmap", "a:1", "--intmap", "b:5", "--filename", "hello.go", - "--sub-field", "hello", + //"--sub-field", "hello", "id", "10", "remaining1", @@ -101,7 +101,7 @@ func Example() { fmt.Printf("PtrSlice: [%v %v]\n", *opts.PtrSlice[0], *opts.PtrSlice[1]) fmt.Printf("IntMap: [a:%v b:%v]\n", opts.IntMap["a"], opts.IntMap["b"]) fmt.Printf("Filename: %v\n", opts.Filename) - fmt.Printf("Interface: %+v\n", opts.Interface) + //fmt.Printf("Interface: %+v\n", opts.Interface) fmt.Printf("Args.ID: %s\n", opts.Args.ID) fmt.Printf("Args.Num: %d\n", opts.Args.Num) fmt.Printf("Args.Rest: %v\n", opts.Args.Rest) @@ -114,7 +114,6 @@ func Example() { // PtrSlice: [hello world] // IntMap: [a:1 b:5] // Filename: hello.go - // Interface: {Sub:hello} // Args.ID: id // Args.Num: 10 // Args.Rest: [remaining1 remaining2] From f0ddc6a846c5b542da14440589dfa9ec08c394ec Mon Sep 17 00:00:00 2001 From: Mauricio Varea Date: Mon, 13 Feb 2017 11:31:22 +0000 Subject: [PATCH 6/8] added types to commands --- command.go | 26 +++++++++++++++++++++++++- help.go | 33 ++++++++++++++++++--------------- parser.go | 13 ++++++++++++- 3 files changed, 55 insertions(+), 17 deletions(-) diff --git a/command.go b/command.go index 2662843..999fec9 100644 --- a/command.go +++ b/command.go @@ -19,6 +19,9 @@ type Command struct { // The name by which the command can be invoked Name string + // Commands can be classified by types + Type string + // The active sub command (set by parsing) or nil Active *Command @@ -34,6 +37,9 @@ type Command struct { commands []*Command hasBuiltinHelpGroup bool args []*Arg + + // Histogram of valid Command types + AllTypes map[string]int } // Commander is an interface which can be implemented by any command added in @@ -147,10 +153,14 @@ func (c *Command) Args() []*Arg { } func newCommand(name string, shortDescription string, longDescription string, data interface{}) *Command { - return &Command{ + ret:= &Command{ Group: newGroup(shortDescription, longDescription, data), Name: name, + Type: "command", + AllTypes: make(map[string]int), } + ret.AllTypes[ret.Type]++ + return ret } func (c *Command) scanSubcommandHandler(parentg *Group) scanHandler { @@ -395,6 +405,20 @@ func (c commandList) Swap(i, j int) { c[i], c[j] = c[j], c[i] } +// selectedVisibleCommands returns a list of commands that are of type 't' and visible. +// if t is empty, it returns all visible commands +func (c *Command) selectedVisibleCommands(t string) []*Command { + cmdList := commandList(c.sortedVisibleCommands()) + var ret commandList + for _, cmd := range cmdList { + if t != "" && cmd.Type != t { + continue + } + ret = append(ret, cmd) + } + return []*Command(ret) +} + func (c *Command) sortedVisibleCommands() []*Command { ret := commandList(c.visibleCommands()) sort.Sort(ret) diff --git a/help.go b/help.go index 1b0c2c6..df07e7c 100644 --- a/help.go +++ b/help.go @@ -268,7 +268,7 @@ func maxCommandLength(s []*Command) int { // option provides a convenient way to add a -h/--help option group to the // command line parser which will automatically show the help messages using // this method. -func (p *Parser) WriteHelp(writer io.Writer) { +func (p *Parser) WriteHelp(writer io.Writer, plural func(s string) string) { if writer == nil { return } @@ -456,28 +456,31 @@ func (p *Parser) WriteHelp(writer io.Writer) { c = c.Active } - scommands := cmd.sortedVisibleCommands() + for t, _ := range cmd.AllTypes { - if len(scommands) > 0 { - maxnamelen := maxCommandLength(scommands) + scommands := cmd.selectedVisibleCommands(t) - fmt.Fprintln(wr) - fmt.Fprintln(wr, "Available commands:") + if len(scommands) > 0 { + maxnamelen := maxCommandLength(scommands) + + fmt.Fprintln(wr) + fmt.Fprintf(wr, "Available %s:\n", plural(t)) + + for _, c := range scommands { + fmt.Fprintf(wr, " %s", c.Name) - for _, c := range scommands { - fmt.Fprintf(wr, " %s", c.Name) + if len(c.ShortDescription) > 0 { + pad := strings.Repeat(" ", maxnamelen - len(c.Name)) + fmt.Fprintf(wr, "%s %s", pad, c.ShortDescription) - if len(c.ShortDescription) > 0 { - pad := strings.Repeat(" ", maxnamelen-len(c.Name)) - fmt.Fprintf(wr, "%s %s", pad, c.ShortDescription) + if len(c.Aliases) > 0 { + fmt.Fprintf(wr, " (aliases: %s)", strings.Join(c.Aliases, ", ")) + } - if len(c.Aliases) > 0 { - fmt.Fprintf(wr, " (aliases: %s)", strings.Join(c.Aliases, ", ")) } + fmt.Fprintln(wr) } - - fmt.Fprintln(wr) } } diff --git a/parser.go b/parser.go index e33c145..9eb2313 100644 --- a/parser.go +++ b/parser.go @@ -52,6 +52,10 @@ type Parser struct { // command to be executed when parsing has finished. CommandHandler func(command Commander, args []string) error + // PluralHandler is a function gets called to handle the formation of + // plurals in the help screen. If not specified, it just appends an 's'. + PluralHandler func(s string) string + internalError error } @@ -675,7 +679,14 @@ func (p *Parser) parseNonOption(s *parseState) error { func (p *Parser) showBuiltinHelp() error { var b bytes.Buffer - p.WriteHelp(&b) + plural := func(s string) string { + return fmt.Sprintf("%ss",s) + } + if p.PluralHandler != nil { + plural = p.PluralHandler + } + + p.WriteHelp(&b, plural) return newError(ErrHelp, b.String()) } From eb96beca2948337f34665f3036481218405d15d8 Mon Sep 17 00:00:00 2001 From: Mauricio Varea Date: Tue, 14 Feb 2017 10:44:51 +0000 Subject: [PATCH 7/8] moved AllTypes to Parser --- command.go | 13 +++++-------- help.go | 9 +++++++-- parser.go | 9 +++++++++ 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/command.go b/command.go index 999fec9..2ea730a 100644 --- a/command.go +++ b/command.go @@ -8,6 +8,8 @@ import ( "unsafe" ) +const DefaultCommandType = "command" + // Command represents an application command. Commands can be added to the // parser (which itself is a command) and are selected/executed when its name // is specified on the command line. The Command type embeds a Group and @@ -37,9 +39,6 @@ type Command struct { commands []*Command hasBuiltinHelpGroup bool args []*Arg - - // Histogram of valid Command types - AllTypes map[string]int } // Commander is an interface which can be implemented by any command added in @@ -153,16 +152,14 @@ func (c *Command) Args() []*Arg { } func newCommand(name string, shortDescription string, longDescription string, data interface{}) *Command { - ret:= &Command{ + return &Command{ Group: newGroup(shortDescription, longDescription, data), Name: name, - Type: "command", - AllTypes: make(map[string]int), + Type: DefaultCommandType, } - ret.AllTypes[ret.Type]++ - return ret } + func (c *Command) scanSubcommandHandler(parentg *Group) scanHandler { f := func(realval reflect.Value, sfield *reflect.StructField) (bool, error) { mtag := newMultiTag(string(sfield.Tag)) diff --git a/help.go b/help.go index df07e7c..cd77326 100644 --- a/help.go +++ b/help.go @@ -12,6 +12,7 @@ import ( "runtime" "strings" "unicode/utf8" + "sort" ) type alignmentInfo struct { @@ -455,8 +456,12 @@ func (p *Parser) WriteHelp(writer io.Writer, plural func(s string) string) { c = c.Active } - - for t, _ := range cmd.AllTypes { + allTypes := make([]string, 0, len(p.AllTypes)) + for t, _ := range p.AllTypes { + allTypes = append(allTypes, t) + } + sort.Strings(allTypes) + for _, t := range allTypes { scommands := cmd.selectedVisibleCommands(t) diff --git a/parser.go b/parser.go index 9eb2313..cdbbcb3 100644 --- a/parser.go +++ b/parser.go @@ -20,6 +20,9 @@ type Parser struct { // Embedded, see Command for more information *Command + // Histogram of valid Command types + AllTypes map[string]int + // A usage string to be displayed in the help message. Usage string @@ -175,14 +178,20 @@ func NewNamedParser(appname string, options Options) *Parser { p := &Parser{ Command: newCommand(appname, "", "", nil), Options: options, + AllTypes: make(map[string]int), NamespaceDelimiter: ".", } + p.AddType(DefaultCommandType) p.Command.parent = p return p } +func (p *Parser) AddType(s string) { + p.AllTypes[s]++ +} + // Parse parses the command line arguments from os.Args using Parser.ParseArgs. // For more detailed information see ParseArgs. func (p *Parser) Parse() ([]string, error) { From a3805e59a10711e19cb057df3530785c23b387ad Mon Sep 17 00:00:00 2001 From: Mauricio Varea Date: Thu, 20 Jul 2017 15:19:23 +0100 Subject: [PATCH 8/8] formatting the code --- command.go | 1 - help.go | 4 ++-- parser.go | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/command.go b/command.go index 2ea730a..2c3acef 100644 --- a/command.go +++ b/command.go @@ -159,7 +159,6 @@ func newCommand(name string, shortDescription string, longDescription string, da } } - func (c *Command) scanSubcommandHandler(parentg *Group) scanHandler { f := func(realval reflect.Value, sfield *reflect.StructField) (bool, error) { mtag := newMultiTag(string(sfield.Tag)) diff --git a/help.go b/help.go index a33bdce..20943bc 100644 --- a/help.go +++ b/help.go @@ -10,9 +10,9 @@ import ( "fmt" "io" "runtime" + "sort" "strings" "unicode/utf8" - "sort" ) type alignmentInfo struct { @@ -481,7 +481,7 @@ func (p *Parser) WriteHelp(writer io.Writer, plural func(s string) string) { fmt.Fprintf(wr, " %s", c.Name) if len(c.ShortDescription) > 0 { - pad := strings.Repeat(" ", maxnamelen - len(c.Name)) + pad := strings.Repeat(" ", maxnamelen-len(c.Name)) fmt.Fprintf(wr, "%s %s", pad, c.ShortDescription) if len(c.Aliases) > 0 { diff --git a/parser.go b/parser.go index 2753749..193b621 100644 --- a/parser.go +++ b/parser.go @@ -690,7 +690,7 @@ func (p *Parser) showBuiltinHelp() error { var b bytes.Buffer plural := func(s string) string { - return fmt.Sprintf("%ss",s) + return fmt.Sprintf("%ss", s) } if p.PluralHandler != nil { plural = p.PluralHandler