package cli import ( "flag" "fmt" "reflect" "sort" "strings" ) // Command is a subcommand for a cli.App. type Command struct { // The name of the command Name string // A list of aliases for the command Aliases []string // A short description of the usage of this command Usage string // Custom text to show on USAGE section of help UsageText string // A longer explanation of how the command works Description string // A short description of the arguments of this command ArgsUsage string // The category the command is part of Category string // The function to call when checking for bash command completions BashComplete BashCompleteFunc // An action to execute before any sub-subcommands are run, but after the context is ready // If a non-nil error is returned, no sub-subcommands are run Before BeforeFunc // An action to execute after any subcommands are run, but after the subcommand has finished // It is run even if Action() panics After AfterFunc // The function to call when this command is invoked Action ActionFunc // Execute this function if a usage error occurs. OnUsageError OnUsageErrorFunc // List of child commands Subcommands []*Command // List of flags to parse Flags []Flag flagCategories FlagCategories // Treat all flags as normal arguments if true SkipFlagParsing bool // Boolean to hide built-in help command and help flag HideHelp bool // Boolean to hide built-in help command but keep help flag // Ignored if HideHelp is true. HideHelpCommand bool // Boolean to hide this command from help or completion Hidden bool // Boolean to enable short-option handling so user can combine several // single-character bool arguments into one // i.e. foobar -o -v -> foobar -ov UseShortOptionHandling bool // Full name of command for help, defaults to full command name, including parent commands. HelpName string commandNamePath []string // CustomHelpTemplate the text template for the command help topic. // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. CustomHelpTemplate string // categories contains the categorized commands and is populated on app startup categories CommandCategories // if this is a root "special" command isRoot bool separator separatorSpec } type Commands []*Command type CommandsByName []*Command func (c CommandsByName) Len() int { return len(c) } func (c CommandsByName) Less(i, j int) bool { return lexicographicLess(c[i].Name, c[j].Name) } func (c CommandsByName) Swap(i, j int) { c[i], c[j] = c[j], c[i] } // FullName returns the full name of the command. // For subcommands this ensures that parent commands are part of the command path func (c *Command) FullName() string { if c.commandNamePath == nil { return c.Name } return strings.Join(c.commandNamePath, " ") } func (cmd *Command) Command(name string) *Command { for _, c := range cmd.Subcommands { if c.HasName(name) { return c } } return nil } func (c *Command) setup(ctx *Context) { if c.Command(helpCommand.Name) == nil && !c.HideHelp { if !c.HideHelpCommand { c.Subcommands = append(c.Subcommands, helpCommand) } } if !c.HideHelp && HelpFlag != nil { // append help to flags c.appendFlag(HelpFlag) } if ctx.App.UseShortOptionHandling { c.UseShortOptionHandling = true } c.categories = newCommandCategories() for _, command := range c.Subcommands { c.categories.AddCommand(command.Category, command) } sort.Sort(c.categories.(*commandCategories)) var newCmds []*Command for _, scmd := range c.Subcommands { if scmd.HelpName == "" { scmd.HelpName = fmt.Sprintf("%s %s", c.HelpName, scmd.Name) } scmd.separator = c.separator newCmds = append(newCmds, scmd) } c.Subcommands = newCmds if c.BashComplete == nil { c.BashComplete = DefaultCompleteWithFlags(c) } } func (c *Command) Run(cCtx *Context, arguments ...string) (err error) { if !c.isRoot { c.setup(cCtx) if err := checkDuplicatedCmds(c); err != nil { return err } } a := args(arguments) set, err := c.parseFlags(&a, cCtx.shellComplete) cCtx.flagSet = set if checkCompletions(cCtx) { return nil } if err != nil { if c.OnUsageError != nil { err = c.OnUsageError(cCtx, err, !c.isRoot) cCtx.App.handleExitCoder(cCtx, err) return err } _, _ = fmt.Fprintf(cCtx.App.Writer, "%s %s\n\n", "Incorrect Usage:", err.Error()) if cCtx.App.Suggest { if suggestion, err := c.suggestFlagFromError(err, ""); err == nil { fmt.Fprintf(cCtx.App.Writer, "%s", suggestion) } } if !c.HideHelp { if c.isRoot { _ = ShowAppHelp(cCtx) } else { _ = ShowCommandHelp(cCtx.parentContext, c.Name) } } return err } if checkHelp(cCtx) { return helpCommand.Action(cCtx) } if c.isRoot && !cCtx.App.HideVersion && checkVersion(cCtx) { ShowVersion(cCtx) return nil } if c.After != nil && !cCtx.shellComplete { defer func() { afterErr := c.After(cCtx) if afterErr != nil { cCtx.App.handleExitCoder(cCtx, err) if err != nil { err = newMultiError(err, afterErr) } else { err = afterErr } } }() } cerr := cCtx.checkRequiredFlags(c.Flags) if cerr != nil { _ = helpCommand.Action(cCtx) return cerr } if c.Before != nil && !cCtx.shellComplete { beforeErr := c.Before(cCtx) if beforeErr != nil { cCtx.App.handleExitCoder(cCtx, beforeErr) err = beforeErr return err } } if err = runFlagActions(cCtx, c.Flags); err != nil { return err } var cmd *Command args := cCtx.Args() if args.Present() { name := args.First() cmd = c.Command(name) if cmd == nil { hasDefault := cCtx.App.DefaultCommand != "" isFlagName := checkStringSliceIncludes(name, cCtx.FlagNames()) var ( isDefaultSubcommand = false defaultHasSubcommands = false ) if hasDefault { dc := cCtx.App.Command(cCtx.App.DefaultCommand) defaultHasSubcommands = len(dc.Subcommands) > 0 for _, dcSub := range dc.Subcommands { if checkStringSliceIncludes(name, dcSub.Names()) { isDefaultSubcommand = true break } } } if isFlagName || (hasDefault && (defaultHasSubcommands && isDefaultSubcommand)) { argsWithDefault := cCtx.App.argsWithDefaultCommand(args) if !reflect.DeepEqual(args, argsWithDefault) { cmd = cCtx.App.rootCommand.Command(argsWithDefault.First()) } } } } else if c.isRoot && cCtx.App.DefaultCommand != "" { if dc := cCtx.App.Command(cCtx.App.DefaultCommand); dc != c { cmd = dc } } if cmd != nil { newcCtx := NewContext(cCtx.App, nil, cCtx) newcCtx.Command = cmd return cmd.Run(newcCtx, cCtx.Args().Slice()...) } if c.Action == nil { c.Action = helpCommand.Action } err = c.Action(cCtx) cCtx.App.handleExitCoder(cCtx, err) return err } func (c *Command) newFlagSet() (*flag.FlagSet, error) { return flagSet(c.Name, c.Flags, c.separator) } func (c *Command) useShortOptionHandling() bool { return c.UseShortOptionHandling } func (c *Command) suggestFlagFromError(err error, command string) (string, error) { flag, parseErr := flagFromError(err) if parseErr != nil { return "", err } flags := c.Flags hideHelp := c.HideHelp if command != "" { cmd := c.Command(command) if cmd == nil { return "", err } flags = cmd.Flags hideHelp = hideHelp || cmd.HideHelp } suggestion := SuggestFlag(flags, flag, hideHelp) if len(suggestion) == 0 { return "", err } return fmt.Sprintf(SuggestDidYouMeanTemplate, suggestion) + "\n\n", nil } func (c *Command) parseFlags(args Args, shellComplete bool) (*flag.FlagSet, error) { set, err := c.newFlagSet() if err != nil { return nil, err } if c.SkipFlagParsing { return set, set.Parse(append([]string{"--"}, args.Tail()...)) } err = parseIter(set, c, args.Tail(), shellComplete) if err != nil { return nil, err } err = normalizeFlags(c.Flags, set) if err != nil { return nil, err } return set, nil } // Names returns the names including short names and aliases. func (c *Command) Names() []string { return append([]string{c.Name}, c.Aliases...) } // HasName returns true if Command.Name matches given name func (c *Command) HasName(name string) bool { for _, n := range c.Names() { if n == name { return true } } return false } // VisibleCategories returns a slice of categories and commands that are // Hidden=false func (c *Command) VisibleCategories() []CommandCategory { ret := []CommandCategory{} for _, category := range c.categories.Categories() { if visible := func() CommandCategory { if len(category.VisibleCommands()) > 0 { return category } return nil }(); visible != nil { ret = append(ret, visible) } } return ret } // VisibleCommands returns a slice of the Commands with Hidden=false func (c *Command) VisibleCommands() []*Command { var ret []*Command for _, command := range c.Subcommands { if !command.Hidden { ret = append(ret, command) } } return ret } // VisibleFlagCategories returns a slice containing all the visible flag categories with the flags they contain func (c *Command) VisibleFlagCategories() []VisibleFlagCategory { if c.flagCategories == nil { c.flagCategories = newFlagCategoriesFromFlags(c.Flags) } return c.flagCategories.VisibleCategories() } // VisibleFlags returns a slice of the Flags with Hidden=false func (c *Command) VisibleFlags() []Flag { return visibleFlags(c.Flags) } func (c *Command) appendFlag(fl Flag) { if !hasFlag(c.Flags, fl) { c.Flags = append(c.Flags, fl) } } func hasCommand(commands []*Command, command *Command) bool { for _, existing := range commands { if command == existing { return true } } return false } func checkDuplicatedCmds(parent *Command) error { seen := make(map[string]struct{}) for _, c := range parent.Subcommands { for _, name := range c.Names() { if _, exists := seen[name]; exists { return fmt.Errorf("parent command [%s] has duplicated subcommand name or alias: %s", parent.Name, name) } seen[name] = struct{}{} } } return nil }