...

Source file src/github.com/urfave/cli/v2/command.go

Documentation: github.com/urfave/cli/v2

     1  package cli
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"reflect"
     7  	"sort"
     8  	"strings"
     9  )
    10  
    11  // Command is a subcommand for a cli.App.
    12  type Command struct {
    13  	// The name of the command
    14  	Name string
    15  	// A list of aliases for the command
    16  	Aliases []string
    17  	// A short description of the usage of this command
    18  	Usage string
    19  	// Custom text to show on USAGE section of help
    20  	UsageText string
    21  	// A longer explanation of how the command works
    22  	Description string
    23  	// Whether this command supports arguments
    24  	Args bool
    25  	// A short description of the arguments of this command
    26  	ArgsUsage string
    27  	// The category the command is part of
    28  	Category string
    29  	// The function to call when checking for bash command completions
    30  	BashComplete BashCompleteFunc
    31  	// An action to execute before any sub-subcommands are run, but after the context is ready
    32  	// If a non-nil error is returned, no sub-subcommands are run
    33  	Before BeforeFunc
    34  	// An action to execute after any subcommands are run, but after the subcommand has finished
    35  	// It is run even if Action() panics
    36  	After AfterFunc
    37  	// The function to call when this command is invoked
    38  	Action ActionFunc
    39  	// Execute this function if a usage error occurs.
    40  	OnUsageError OnUsageErrorFunc
    41  	// List of child commands
    42  	Subcommands []*Command
    43  	// List of flags to parse
    44  	Flags          []Flag
    45  	flagCategories FlagCategories
    46  	// Treat all flags as normal arguments if true
    47  	SkipFlagParsing bool
    48  	// Boolean to hide built-in help command and help flag
    49  	HideHelp bool
    50  	// Boolean to hide built-in help command but keep help flag
    51  	// Ignored if HideHelp is true.
    52  	HideHelpCommand bool
    53  	// Boolean to hide this command from help or completion
    54  	Hidden bool
    55  	// Boolean to enable short-option handling so user can combine several
    56  	// single-character bool arguments into one
    57  	// i.e. foobar -o -v -> foobar -ov
    58  	UseShortOptionHandling bool
    59  
    60  	// Full name of command for help, defaults to full command name, including parent commands.
    61  	HelpName        string
    62  	commandNamePath []string
    63  
    64  	// CustomHelpTemplate the text template for the command help topic.
    65  	// cli.go uses text/template to render templates. You can
    66  	// render custom help text by setting this variable.
    67  	CustomHelpTemplate string
    68  
    69  	// categories contains the categorized commands and is populated on app startup
    70  	categories CommandCategories
    71  
    72  	// if this is a root "special" command
    73  	isRoot bool
    74  
    75  	separator separatorSpec
    76  }
    77  
    78  type Commands []*Command
    79  
    80  type CommandsByName []*Command
    81  
    82  func (c CommandsByName) Len() int {
    83  	return len(c)
    84  }
    85  
    86  func (c CommandsByName) Less(i, j int) bool {
    87  	return lexicographicLess(c[i].Name, c[j].Name)
    88  }
    89  
    90  func (c CommandsByName) Swap(i, j int) {
    91  	c[i], c[j] = c[j], c[i]
    92  }
    93  
    94  // FullName returns the full name of the command.
    95  // For subcommands this ensures that parent commands are part of the command path
    96  func (c *Command) FullName() string {
    97  	if c.commandNamePath == nil {
    98  		return c.Name
    99  	}
   100  	return strings.Join(c.commandNamePath, " ")
   101  }
   102  
   103  func (cmd *Command) Command(name string) *Command {
   104  	for _, c := range cmd.Subcommands {
   105  		if c.HasName(name) {
   106  			return c
   107  		}
   108  	}
   109  
   110  	return nil
   111  }
   112  
   113  func (c *Command) setup(ctx *Context) {
   114  	if c.Command(helpCommand.Name) == nil && !c.HideHelp {
   115  		if !c.HideHelpCommand {
   116  			c.Subcommands = append(c.Subcommands, helpCommand)
   117  		}
   118  	}
   119  
   120  	if !c.HideHelp && HelpFlag != nil {
   121  		// append help to flags
   122  		c.appendFlag(HelpFlag)
   123  	}
   124  
   125  	if ctx.App.UseShortOptionHandling {
   126  		c.UseShortOptionHandling = true
   127  	}
   128  
   129  	c.categories = newCommandCategories()
   130  	for _, command := range c.Subcommands {
   131  		c.categories.AddCommand(command.Category, command)
   132  	}
   133  	sort.Sort(c.categories.(*commandCategories))
   134  
   135  	var newCmds []*Command
   136  	for _, scmd := range c.Subcommands {
   137  		if scmd.HelpName == "" {
   138  			scmd.HelpName = fmt.Sprintf("%s %s", c.HelpName, scmd.Name)
   139  		}
   140  		scmd.separator = c.separator
   141  		newCmds = append(newCmds, scmd)
   142  	}
   143  	c.Subcommands = newCmds
   144  
   145  	if c.BashComplete == nil {
   146  		c.BashComplete = DefaultCompleteWithFlags(c)
   147  	}
   148  }
   149  
   150  func (c *Command) Run(cCtx *Context, arguments ...string) (err error) {
   151  
   152  	if !c.isRoot {
   153  		c.setup(cCtx)
   154  		if err := checkDuplicatedCmds(c); err != nil {
   155  			return err
   156  		}
   157  	}
   158  
   159  	a := args(arguments)
   160  	set, err := c.parseFlags(&a, cCtx.shellComplete)
   161  	cCtx.flagSet = set
   162  
   163  	if checkCompletions(cCtx) {
   164  		return nil
   165  	}
   166  
   167  	if err != nil {
   168  		if c.OnUsageError != nil {
   169  			err = c.OnUsageError(cCtx, err, !c.isRoot)
   170  			cCtx.App.handleExitCoder(cCtx, err)
   171  			return err
   172  		}
   173  		_, _ = fmt.Fprintf(cCtx.App.Writer, "%s %s\n\n", "Incorrect Usage:", err.Error())
   174  		if cCtx.App.Suggest {
   175  			if suggestion, err := c.suggestFlagFromError(err, ""); err == nil {
   176  				fmt.Fprintf(cCtx.App.Writer, "%s", suggestion)
   177  			}
   178  		}
   179  		if !c.HideHelp {
   180  			if c.isRoot {
   181  				_ = ShowAppHelp(cCtx)
   182  			} else {
   183  				_ = ShowCommandHelp(cCtx.parentContext, c.Name)
   184  			}
   185  		}
   186  		return err
   187  	}
   188  
   189  	if checkHelp(cCtx) {
   190  		return helpCommand.Action(cCtx)
   191  	}
   192  
   193  	if c.isRoot && !cCtx.App.HideVersion && checkVersion(cCtx) {
   194  		ShowVersion(cCtx)
   195  		return nil
   196  	}
   197  
   198  	if c.After != nil && !cCtx.shellComplete {
   199  		defer func() {
   200  			afterErr := c.After(cCtx)
   201  			if afterErr != nil {
   202  				cCtx.App.handleExitCoder(cCtx, err)
   203  				if err != nil {
   204  					err = newMultiError(err, afterErr)
   205  				} else {
   206  					err = afterErr
   207  				}
   208  			}
   209  		}()
   210  	}
   211  
   212  	cerr := cCtx.checkRequiredFlags(c.Flags)
   213  	if cerr != nil {
   214  		_ = helpCommand.Action(cCtx)
   215  		return cerr
   216  	}
   217  
   218  	if c.Before != nil && !cCtx.shellComplete {
   219  		beforeErr := c.Before(cCtx)
   220  		if beforeErr != nil {
   221  			cCtx.App.handleExitCoder(cCtx, beforeErr)
   222  			err = beforeErr
   223  			return err
   224  		}
   225  	}
   226  
   227  	if err = runFlagActions(cCtx, c.Flags); err != nil {
   228  		return err
   229  	}
   230  
   231  	var cmd *Command
   232  	args := cCtx.Args()
   233  	if args.Present() {
   234  		name := args.First()
   235  		cmd = c.Command(name)
   236  		if cmd == nil {
   237  			hasDefault := cCtx.App.DefaultCommand != ""
   238  			isFlagName := checkStringSliceIncludes(name, cCtx.FlagNames())
   239  
   240  			var (
   241  				isDefaultSubcommand   = false
   242  				defaultHasSubcommands = false
   243  			)
   244  
   245  			if hasDefault {
   246  				dc := cCtx.App.Command(cCtx.App.DefaultCommand)
   247  				defaultHasSubcommands = len(dc.Subcommands) > 0
   248  				for _, dcSub := range dc.Subcommands {
   249  					if checkStringSliceIncludes(name, dcSub.Names()) {
   250  						isDefaultSubcommand = true
   251  						break
   252  					}
   253  				}
   254  			}
   255  
   256  			if isFlagName || (hasDefault && (defaultHasSubcommands && isDefaultSubcommand)) {
   257  				argsWithDefault := cCtx.App.argsWithDefaultCommand(args)
   258  				if !reflect.DeepEqual(args, argsWithDefault) {
   259  					cmd = cCtx.App.rootCommand.Command(argsWithDefault.First())
   260  				}
   261  			}
   262  		}
   263  	} else if c.isRoot && cCtx.App.DefaultCommand != "" {
   264  		if dc := cCtx.App.Command(cCtx.App.DefaultCommand); dc != c {
   265  			cmd = dc
   266  		}
   267  	}
   268  
   269  	if cmd != nil {
   270  		newcCtx := NewContext(cCtx.App, nil, cCtx)
   271  		newcCtx.Command = cmd
   272  		return cmd.Run(newcCtx, cCtx.Args().Slice()...)
   273  	}
   274  
   275  	if c.Action == nil {
   276  		c.Action = helpCommand.Action
   277  	}
   278  
   279  	err = c.Action(cCtx)
   280  
   281  	cCtx.App.handleExitCoder(cCtx, err)
   282  	return err
   283  }
   284  
   285  func (c *Command) newFlagSet() (*flag.FlagSet, error) {
   286  	return flagSet(c.Name, c.Flags, c.separator)
   287  }
   288  
   289  func (c *Command) useShortOptionHandling() bool {
   290  	return c.UseShortOptionHandling
   291  }
   292  
   293  func (c *Command) suggestFlagFromError(err error, command string) (string, error) {
   294  	flag, parseErr := flagFromError(err)
   295  	if parseErr != nil {
   296  		return "", err
   297  	}
   298  
   299  	flags := c.Flags
   300  	hideHelp := c.HideHelp
   301  	if command != "" {
   302  		cmd := c.Command(command)
   303  		if cmd == nil {
   304  			return "", err
   305  		}
   306  		flags = cmd.Flags
   307  		hideHelp = hideHelp || cmd.HideHelp
   308  	}
   309  
   310  	suggestion := SuggestFlag(flags, flag, hideHelp)
   311  	if len(suggestion) == 0 {
   312  		return "", err
   313  	}
   314  
   315  	return fmt.Sprintf(SuggestDidYouMeanTemplate, suggestion) + "\n\n", nil
   316  }
   317  
   318  func (c *Command) parseFlags(args Args, shellComplete bool) (*flag.FlagSet, error) {
   319  	set, err := c.newFlagSet()
   320  	if err != nil {
   321  		return nil, err
   322  	}
   323  
   324  	if c.SkipFlagParsing {
   325  		return set, set.Parse(append([]string{"--"}, args.Tail()...))
   326  	}
   327  
   328  	err = parseIter(set, c, args.Tail(), shellComplete)
   329  	if err != nil {
   330  		return nil, err
   331  	}
   332  
   333  	err = normalizeFlags(c.Flags, set)
   334  	if err != nil {
   335  		return nil, err
   336  	}
   337  
   338  	return set, nil
   339  }
   340  
   341  // Names returns the names including short names and aliases.
   342  func (c *Command) Names() []string {
   343  	return append([]string{c.Name}, c.Aliases...)
   344  }
   345  
   346  // HasName returns true if Command.Name matches given name
   347  func (c *Command) HasName(name string) bool {
   348  	for _, n := range c.Names() {
   349  		if n == name {
   350  			return true
   351  		}
   352  	}
   353  	return false
   354  }
   355  
   356  // VisibleCategories returns a slice of categories and commands that are
   357  // Hidden=false
   358  func (c *Command) VisibleCategories() []CommandCategory {
   359  	ret := []CommandCategory{}
   360  	for _, category := range c.categories.Categories() {
   361  		if visible := func() CommandCategory {
   362  			if len(category.VisibleCommands()) > 0 {
   363  				return category
   364  			}
   365  			return nil
   366  		}(); visible != nil {
   367  			ret = append(ret, visible)
   368  		}
   369  	}
   370  	return ret
   371  }
   372  
   373  // VisibleCommands returns a slice of the Commands with Hidden=false
   374  func (c *Command) VisibleCommands() []*Command {
   375  	var ret []*Command
   376  	for _, command := range c.Subcommands {
   377  		if !command.Hidden {
   378  			ret = append(ret, command)
   379  		}
   380  	}
   381  	return ret
   382  }
   383  
   384  // VisibleFlagCategories returns a slice containing all the visible flag categories with the flags they contain
   385  func (c *Command) VisibleFlagCategories() []VisibleFlagCategory {
   386  	if c.flagCategories == nil {
   387  		c.flagCategories = newFlagCategoriesFromFlags(c.Flags)
   388  	}
   389  	return c.flagCategories.VisibleCategories()
   390  }
   391  
   392  // VisibleFlags returns a slice of the Flags with Hidden=false
   393  func (c *Command) VisibleFlags() []Flag {
   394  	return visibleFlags(c.Flags)
   395  }
   396  
   397  func (c *Command) appendFlag(fl Flag) {
   398  	if !hasFlag(c.Flags, fl) {
   399  		c.Flags = append(c.Flags, fl)
   400  	}
   401  }
   402  
   403  func hasCommand(commands []*Command, command *Command) bool {
   404  	for _, existing := range commands {
   405  		if command == existing {
   406  			return true
   407  		}
   408  	}
   409  
   410  	return false
   411  }
   412  
   413  func checkDuplicatedCmds(parent *Command) error {
   414  	seen := make(map[string]struct{})
   415  	for _, c := range parent.Subcommands {
   416  		for _, name := range c.Names() {
   417  			if _, exists := seen[name]; exists {
   418  				return fmt.Errorf("parent command [%s] has duplicated subcommand name or alias: %s", parent.Name, name)
   419  			}
   420  			seen[name] = struct{}{}
   421  		}
   422  	}
   423  	return nil
   424  }
   425  

View as plain text