package flags import ( "reflect" "sort" "strconv" "strings" ) // 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 // therefore also carries a set of command specific options. type Command struct { // Embedded, see Group for more information *Group // The name by which the command can be invoked Name string // The active sub command (set by parsing) or nil Active *Command // Whether subcommands are optional SubcommandsOptional bool // Aliases for the command Aliases []string // Whether positional arguments are required ArgsRequired bool commands []*Command hasBuiltinHelpGroup bool args []*Arg } // Commander is an interface which can be implemented by any command added in // the options. When implemented, the Execute method will be called for the last // specified (sub)command providing the remaining command line arguments. type Commander interface { // Execute will be called for the last active (sub)command. The // args argument contains the remaining command line arguments. The // error that Execute returns will be eventually passed out of the // Parse method of the Parser. Execute(args []string) error } // Usage is an interface which can be implemented to show a custom usage string // in the help message shown for a command. type Usage interface { // Usage is called for commands to allow customized printing of command // usage in the generated help message. Usage() string } type lookup struct { shortNames map[string]*Option longNames map[string]*Option commands map[string]*Command } // AddCommand adds a new command to the parser with the given name and data. The // data needs to be a pointer to a struct from which the fields indicate which // options are in the command. The provided data can implement the Command and // Usage interfaces. func (c *Command) AddCommand(command string, shortDescription string, longDescription string, data interface{}) (*Command, error) { cmd := newCommand(command, shortDescription, longDescription, data) cmd.parent = c if err := cmd.scan(); err != nil { return nil, err } c.commands = append(c.commands, cmd) return cmd, nil } // AddGroup adds a new group to the command with the given name and data. The // data needs to be a pointer to a struct from which the fields indicate which // options are in the group. func (c *Command) AddGroup(shortDescription string, longDescription string, data interface{}) (*Group, error) { group := newGroup(shortDescription, longDescription, data) group.parent = c if err := group.scanType(c.scanSubcommandHandler(group)); err != nil { return nil, err } c.groups = append(c.groups, group) return group, nil } // Commands returns a list of subcommands of this command. func (c *Command) Commands() []*Command { return c.commands } // Find locates the subcommand with the given name and returns it. If no such // command can be found Find will return nil. func (c *Command) Find(name string) *Command { for _, cc := range c.commands { if cc.match(name) { return cc } } return nil } // FindOptionByLongName finds an option that is part of the command, or any of // its parent commands, by matching its long name (including the option // namespace). func (c *Command) FindOptionByLongName(longName string) (option *Option) { for option == nil && c != nil { option = c.Group.FindOptionByLongName(longName) c, _ = c.parent.(*Command) } return option } // FindOptionByShortName finds an option that is part of the command, or any of // its parent commands, by matching its long name (including the option // namespace). func (c *Command) FindOptionByShortName(shortName rune) (option *Option) { for option == nil && c != nil { option = c.Group.FindOptionByShortName(shortName) c, _ = c.parent.(*Command) } return option } // Args returns a list of positional arguments associated with this command. func (c *Command) Args() []*Arg { ret := make([]*Arg, len(c.args)) copy(ret, c.args) return ret } func newCommand(name string, shortDescription string, longDescription string, data interface{}) *Command { return &Command{ Group: newGroup(shortDescription, longDescription, data), Name: name, } } func (c *Command) scanSubcommandHandler(parentg *Group) scanHandler { f := func(realval reflect.Value, sfield *reflect.StructField) (bool, error) { mtag := newMultiTag(string(sfield.Tag)) if err := mtag.Parse(); err != nil { return true, err } positional := mtag.Get("positional-args") if len(positional) != 0 { stype := realval.Type() for i := 0; i < stype.NumField(); i++ { field := stype.Field(i) m := newMultiTag((string(field.Tag))) if err := m.Parse(); err != nil { return true, err } name := m.Get("positional-arg-name") if len(name) == 0 { name = field.Name } required := -1 requiredMaximum := -1 sreq := m.Get("required") if sreq != "" { required = 1 rng := strings.SplitN(sreq, "-", 2) if len(rng) > 1 { if preq, err := strconv.ParseInt(rng[0], 10, 32); err == nil { required = int(preq) } if preq, err := strconv.ParseInt(rng[1], 10, 32); err == nil { requiredMaximum = int(preq) } } else { if preq, err := strconv.ParseInt(sreq, 10, 32); err == nil { required = int(preq) } } } arg := &Arg{ Name: name, Description: m.Get("description"), Required: required, RequiredMaximum: requiredMaximum, value: realval.Field(i), tag: m, } c.args = append(c.args, arg) if len(mtag.Get("required")) != 0 { c.ArgsRequired = true } } return true, nil } subcommand := mtag.Get("command") if len(subcommand) != 0 { var ptrval reflect.Value if realval.Kind() == reflect.Ptr { ptrval = realval if ptrval.IsNil() { ptrval.Set(reflect.New(ptrval.Type().Elem())) } } else { ptrval = realval.Addr() } shortDescription := mtag.Get("description") longDescription := mtag.Get("long-description") subcommandsOptional := mtag.Get("subcommands-optional") aliases := mtag.GetMany("alias") subc, err := c.AddCommand(subcommand, shortDescription, longDescription, ptrval.Interface()) if err != nil { return true, err } subc.Hidden = mtag.Get("hidden") != "" if len(subcommandsOptional) > 0 { subc.SubcommandsOptional = true } if len(aliases) > 0 { subc.Aliases = aliases } return true, nil } return parentg.scanSubGroupHandler(realval, sfield) } return f } func (c *Command) scan() error { return c.scanType(c.scanSubcommandHandler(c.Group)) } func (c *Command) eachOption(f func(*Command, *Group, *Option)) { c.eachCommand(func(c *Command) { c.eachGroup(func(g *Group) { for _, option := range g.options { f(c, g, option) } }) }, true) } func (c *Command) eachCommand(f func(*Command), recurse bool) { f(c) for _, cc := range c.commands { if recurse { cc.eachCommand(f, true) } else { f(cc) } } } func (c *Command) eachActiveGroup(f func(cc *Command, g *Group)) { c.eachGroup(func(g *Group) { f(c, g) }) if c.Active != nil { c.Active.eachActiveGroup(f) } } func (c *Command) addHelpGroups(showHelp func() error) { if !c.hasBuiltinHelpGroup { c.addHelpGroup(showHelp) c.hasBuiltinHelpGroup = true } for _, cc := range c.commands { cc.addHelpGroups(showHelp) } } func (c *Command) makeLookup() lookup { ret := lookup{ shortNames: make(map[string]*Option), longNames: make(map[string]*Option), commands: make(map[string]*Command), } parent := c.parent var parents []*Command for parent != nil { if cmd, ok := parent.(*Command); ok { parents = append(parents, cmd) parent = cmd.parent } else { parent = nil } } for i := len(parents) - 1; i >= 0; i-- { parents[i].fillLookup(&ret, true) } c.fillLookup(&ret, false) return ret } func (c *Command) fillLookup(ret *lookup, onlyOptions bool) { c.eachGroup(func(g *Group) { for _, option := range g.options { if option.ShortName != 0 { ret.shortNames[string(option.ShortName)] = option } if len(option.LongName) > 0 { ret.longNames[option.LongNameWithNamespace()] = option } } }) if onlyOptions { return } for _, subcommand := range c.commands { ret.commands[subcommand.Name] = subcommand for _, a := range subcommand.Aliases { ret.commands[a] = subcommand } } } func (c *Command) groupByName(name string) *Group { if grp := c.Group.groupByName(name); grp != nil { return grp } for _, subc := range c.commands { prefix := subc.Name + "." if strings.HasPrefix(name, prefix) { if grp := subc.groupByName(name[len(prefix):]); grp != nil { return grp } } else if name == subc.Name { return subc.Group } } return nil } type commandList []*Command func (c commandList) Less(i, j int) bool { return c[i].Name < c[j].Name } func (c commandList) Len() int { return len(c) } func (c commandList) Swap(i, j int) { c[i], c[j] = c[j], c[i] } func (c *Command) sortedVisibleCommands() []*Command { ret := commandList(c.visibleCommands()) sort.Sort(ret) return []*Command(ret) } func (c *Command) visibleCommands() []*Command { ret := make([]*Command, 0, len(c.commands)) for _, cmd := range c.commands { if !cmd.Hidden { ret = append(ret, cmd) } } return ret } func (c *Command) match(name string) bool { if c.Name == name { return true } for _, v := range c.Aliases { if v == name { return true } } return false } func (c *Command) hasCliOptions() bool { ret := false c.eachGroup(func(g *Group) { if g.isBuiltinHelp { return } for _, opt := range g.options { if opt.canCli() { ret = true } } }) return ret } func (c *Command) fillParseState(s *parseState) { s.positional = make([]*Arg, len(c.args)) copy(s.positional, c.args) s.lookup = c.makeLookup() s.command = c }