| 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 |
| // 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) |
| |
| context := NewContext(ctx.App, set, ctx) |
| context.Command = c |
| if checkCommandCompletions(context, c.Name) { |
| return nil |
| } |
| |
| if err != nil { |
| if c.OnUsageError != nil { |
| err = c.OnUsageError(context, err, false) |
| context.App.handleExitCoder(context, err) |
| return err |
| } |
| _, _ = fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error()) |
| _, _ = fmt.Fprintln(context.App.Writer) |
| _ = ShowCommandHelp(context, c.Name) |
| return err |
| } |
| |
| if checkCommandHelp(context, c.Name) { |
| return nil |
| } |
| |
| cerr := checkRequiredFlags(c.Flags, context) |
| if cerr != nil { |
| _ = ShowCommandHelp(context, c.Name) |
| return cerr |
| } |
| |
| if c.After != nil { |
| defer func() { |
| afterErr := c.After(context) |
| if afterErr != nil { |
| context.App.handleExitCoder(context, err) |
| if err != nil { |
| err = newMultiError(err, afterErr) |
| } else { |
| err = afterErr |
| } |
| } |
| }() |
| } |
| |
| if c.Before != nil { |
| err = c.Before(context) |
| if err != nil { |
| _ = ShowCommandHelp(context, c.Name) |
| context.App.handleExitCoder(context, err) |
| return err |
| } |
| } |
| |
| if c.Action == nil { |
| c.Action = helpSubcommand.Action |
| } |
| |
| context.Command = c |
| err = c.Action(context) |
| |
| if err != nil { |
| context.App.handleExitCoder(context, 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.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 = ctx.App.HideVersion |
| app.Compiled = ctx.App.Compiled |
| app.Writer = ctx.App.Writer |
| app.ErrWriter = ctx.App.ErrWriter |
| app.ExitErrHandler = ctx.App.ExitErrHandler |
| app.UseShortOptionHandling = ctx.App.UseShortOptionHandling |
| |
| 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 = helpSubcommand.Action |
| } |
| app.OnUsageError = c.OnUsageError |
| |
| for index, cc := range app.Commands { |
| app.Commands[index].commandNamePath = []string{c.Name, cc.Name} |
| } |
| |
| return app.RunAsSubcommand(ctx) |
| } |
| |
| // 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 |
| } |