|
|
- 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
- }
|