package cli import ( "flag" "fmt" "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 } 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, " ") } // Run invokes the command given the context, parses ctx.Args() to generate command-specific flags func (c *Command) Run(ctx *Context) (err error) { if len(c.Subcommands) > 0 { return c.startApp(ctx) } if !c.HideHelp && HelpFlag != nil { // append help to flags c.appendFlag(HelpFlag) } if ctx.App.UseShortOptionHandling { c.UseShortOptionHandling = true } set, err := c.parseFlags(ctx.Args(), ctx.shellComplete) cCtx := NewContext(ctx.App, set, ctx) cCtx.Command = c if checkCommandCompletions(cCtx, c.Name) { return nil } if err != nil { if c.OnUsageError != nil { err = c.OnUsageError(cCtx, err, false) cCtx.App.handleExitCoder(cCtx, err) return err } _, _ = fmt.Fprintln(cCtx.App.Writer, "Incorrect Usage:", err.Error()) _, _ = fmt.Fprintln(cCtx.App.Writer) if ctx.App.Suggest { if suggestion, err := ctx.App.suggestFlagFromError(err, c.Name); err == nil { fmt.Fprintf(cCtx.App.Writer, suggestion) } } if !c.HideHelp { _ = ShowCommandHelp(cCtx, c.Name) } return err } if checkCommandHelp(cCtx, c.Name) { return nil } cerr := cCtx.checkRequiredFlags(c.Flags) if cerr != nil { if !c.HideHelp { _ = ShowCommandHelp(cCtx, c.Name) } return cerr } if c.After != nil { defer func() { afterErr := c.After(cCtx) if afterErr != nil { cCtx.App.handleExitCoder(cCtx, err) if err != nil { err = newMultiError(err, afterErr) } else { err = afterErr } } }() } if c.Before != nil { err = c.Before(cCtx) if err != nil { cCtx.App.handleExitCoder(cCtx, err) return err } } if err = runFlagActions(cCtx, c.Flags); err != nil { return err } if c.Action == nil { c.Action = helpCommand.Action } cCtx.Command = c err = c.Action(cCtx) if err != nil { cCtx.App.handleExitCoder(cCtx, err) } return err } func (c *Command) newFlagSet() (*flag.FlagSet, error) { return flagSet(c.Name, c.Flags) } func (c *Command) useShortOptionHandling() bool { return c.UseShortOptionHandling } 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 } func (c *Command) startApp(ctx *Context) error { app := &App{ Metadata: ctx.App.Metadata, Name: fmt.Sprintf("%s %s", ctx.App.Name, c.Name), } if c.HelpName == "" { app.HelpName = c.HelpName } else { app.HelpName = app.Name } app.Usage = c.Usage app.UsageText = c.UsageText app.Description = c.Description app.ArgsUsage = c.ArgsUsage // set CommandNotFound app.CommandNotFound = ctx.App.CommandNotFound app.CustomAppHelpTemplate = c.CustomHelpTemplate // set the flags and commands app.Commands = c.Subcommands app.Flags = c.Flags app.HideHelp = c.HideHelp app.HideHelpCommand = c.HideHelpCommand app.Version = ctx.App.Version app.HideVersion = true app.Compiled = ctx.App.Compiled app.Reader = ctx.App.Reader app.Writer = ctx.App.Writer app.ErrWriter = ctx.App.ErrWriter app.ExitErrHandler = ctx.App.ExitErrHandler app.UseShortOptionHandling = ctx.App.UseShortOptionHandling app.Suggest = ctx.App.Suggest app.categories = newCommandCategories() for _, command := range c.Subcommands { app.categories.AddCommand(command.Category, command) } sort.Sort(app.categories.(*commandCategories)) // bash completion app.EnableBashCompletion = ctx.App.EnableBashCompletion if c.BashComplete != nil { app.BashComplete = c.BashComplete } // set the actions app.Before = c.Before app.After = c.After if c.Action != nil { app.Action = c.Action } else { app.Action = helpCommand.Action } app.OnUsageError = c.OnUsageError for index, cc := range app.Commands { app.Commands[index].commandNamePath = []string{c.Name, cc.Name} } return app.RunAsSubcommand(ctx) } // 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 = newFlagCategories() for _, fl := range c.Flags { if cf, ok := fl.(CategorizableFlag); ok { c.flagCategories.AddFlag(cf.GetCategory(), fl) } } } 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 }