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