1package cli 2 3import ( 4 "fmt" 5 "io/ioutil" 6 "sort" 7 "strings" 8) 9 10// Command is a subcommand for a cli.App. 11type Command struct { 12 // The name of the command 13 Name string 14 // short name of the command. Typically one character (deprecated, use `Aliases`) 15 ShortName string 16 // A list of aliases for the command 17 Aliases []string 18 // A short description of the usage of this command 19 Usage string 20 // Custom text to show on USAGE section of help 21 UsageText string 22 // A longer explanation of how the command works 23 Description string 24 // A short description of the arguments of this command 25 ArgsUsage string 26 // The category the command is part of 27 Category string 28 // The function to call when checking for bash command completions 29 BashComplete BashCompleteFunc 30 // An action to execute before any sub-subcommands are run, but after the context is ready 31 // If a non-nil error is returned, no sub-subcommands are run 32 Before BeforeFunc 33 // An action to execute after any subcommands are run, but after the subcommand has finished 34 // It is run even if Action() panics 35 After AfterFunc 36 // The function to call when this command is invoked 37 Action interface{} 38 // TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind 39 // of deprecation period has passed, maybe? 40 41 // Execute this function if a usage error occurs. 42 OnUsageError OnUsageErrorFunc 43 // List of child commands 44 Subcommands Commands 45 // List of flags to parse 46 Flags []Flag 47 // Treat all flags as normal arguments if true 48 SkipFlagParsing bool 49 // Boolean to hide built-in help command 50 HideHelp bool 51 // Boolean to hide this command from help or completion 52 Hidden bool 53 54 // Full name of command for help, defaults to full command name, including parent commands. 55 HelpName string 56 commandNamePath []string 57} 58 59// FullName returns the full name of the command. 60// For subcommands this ensures that parent commands are part of the command path 61func (c Command) FullName() string { 62 if c.commandNamePath == nil { 63 return c.Name 64 } 65 return strings.Join(c.commandNamePath, " ") 66} 67 68// Commands is a slice of Command 69type Commands []Command 70 71// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags 72func (c Command) Run(ctx *Context) (err error) { 73 if len(c.Subcommands) > 0 { 74 return c.startApp(ctx) 75 } 76 77 if !c.HideHelp && (HelpFlag != BoolFlag{}) { 78 // append help to flags 79 c.Flags = append( 80 c.Flags, 81 HelpFlag, 82 ) 83 } 84 85 if ctx.App.EnableBashCompletion { 86 c.Flags = append(c.Flags, BashCompletionFlag) 87 } 88 89 set := flagSet(c.Name, c.Flags) 90 set.SetOutput(ioutil.Discard) 91 92 if !c.SkipFlagParsing { 93 firstFlagIndex := -1 94 terminatorIndex := -1 95 for index, arg := range ctx.Args() { 96 if arg == "--" { 97 terminatorIndex = index 98 break 99 } else if arg == "-" { 100 // Do nothing. A dash alone is not really a flag. 101 continue 102 } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { 103 firstFlagIndex = index 104 } 105 } 106 107 if firstFlagIndex > -1 { 108 args := ctx.Args() 109 regularArgs := make([]string, len(args[1:firstFlagIndex])) 110 copy(regularArgs, args[1:firstFlagIndex]) 111 112 var flagArgs []string 113 if terminatorIndex > -1 { 114 flagArgs = args[firstFlagIndex:terminatorIndex] 115 regularArgs = append(regularArgs, args[terminatorIndex:]...) 116 } else { 117 flagArgs = args[firstFlagIndex:] 118 } 119 120 err = set.Parse(append(flagArgs, regularArgs...)) 121 } else { 122 err = set.Parse(ctx.Args().Tail()) 123 } 124 } else { 125 if c.SkipFlagParsing { 126 err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...)) 127 } 128 } 129 130 if err != nil { 131 if c.OnUsageError != nil { 132 err := c.OnUsageError(ctx, err, false) 133 HandleExitCoder(err) 134 return err 135 } 136 fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") 137 fmt.Fprintln(ctx.App.Writer) 138 ShowCommandHelp(ctx, c.Name) 139 return err 140 } 141 142 nerr := normalizeFlags(c.Flags, set) 143 if nerr != nil { 144 fmt.Fprintln(ctx.App.Writer, nerr) 145 fmt.Fprintln(ctx.App.Writer) 146 ShowCommandHelp(ctx, c.Name) 147 return nerr 148 } 149 150 context := NewContext(ctx.App, set, ctx) 151 152 if checkCommandCompletions(context, c.Name) { 153 return nil 154 } 155 156 if checkCommandHelp(context, c.Name) { 157 return nil 158 } 159 160 if c.After != nil { 161 defer func() { 162 afterErr := c.After(context) 163 if afterErr != nil { 164 HandleExitCoder(err) 165 if err != nil { 166 err = NewMultiError(err, afterErr) 167 } else { 168 err = afterErr 169 } 170 } 171 }() 172 } 173 174 if c.Before != nil { 175 err = c.Before(context) 176 if err != nil { 177 fmt.Fprintln(ctx.App.Writer, err) 178 fmt.Fprintln(ctx.App.Writer) 179 ShowCommandHelp(ctx, c.Name) 180 HandleExitCoder(err) 181 return err 182 } 183 } 184 185 context.Command = c 186 err = HandleAction(c.Action, context) 187 188 if err != nil { 189 HandleExitCoder(err) 190 } 191 return err 192} 193 194// Names returns the names including short names and aliases. 195func (c Command) Names() []string { 196 names := []string{c.Name} 197 198 if c.ShortName != "" { 199 names = append(names, c.ShortName) 200 } 201 202 return append(names, c.Aliases...) 203} 204 205// HasName returns true if Command.Name or Command.ShortName matches given name 206func (c Command) HasName(name string) bool { 207 for _, n := range c.Names() { 208 if n == name { 209 return true 210 } 211 } 212 return false 213} 214 215func (c Command) startApp(ctx *Context) error { 216 app := NewApp() 217 app.Metadata = ctx.App.Metadata 218 // set the name and usage 219 app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) 220 if c.HelpName == "" { 221 app.HelpName = c.HelpName 222 } else { 223 app.HelpName = app.Name 224 } 225 226 if c.Description != "" { 227 app.Usage = c.Description 228 } else { 229 app.Usage = c.Usage 230 } 231 232 // set CommandNotFound 233 app.CommandNotFound = ctx.App.CommandNotFound 234 235 // set the flags and commands 236 app.Commands = c.Subcommands 237 app.Flags = c.Flags 238 app.HideHelp = c.HideHelp 239 240 app.Version = ctx.App.Version 241 app.HideVersion = ctx.App.HideVersion 242 app.Compiled = ctx.App.Compiled 243 app.Author = ctx.App.Author 244 app.Email = ctx.App.Email 245 app.Writer = ctx.App.Writer 246 247 app.categories = CommandCategories{} 248 for _, command := range c.Subcommands { 249 app.categories = app.categories.AddCommand(command.Category, command) 250 } 251 252 sort.Sort(app.categories) 253 254 // bash completion 255 app.EnableBashCompletion = ctx.App.EnableBashCompletion 256 if c.BashComplete != nil { 257 app.BashComplete = c.BashComplete 258 } 259 260 // set the actions 261 app.Before = c.Before 262 app.After = c.After 263 if c.Action != nil { 264 app.Action = c.Action 265 } else { 266 app.Action = helpSubcommand.Action 267 } 268 269 for index, cc := range app.Commands { 270 app.Commands[index].commandNamePath = []string{c.Name, cc.Name} 271 } 272 273 return app.RunAsSubcommand(ctx) 274} 275 276// VisibleFlags returns a slice of the Flags with Hidden=false 277func (c Command) VisibleFlags() []Flag { 278 return visibleFlags(c.Flags) 279} 280