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 // Skip argument reordering which attempts to move flags before arguments, 50 // but only works if all flags appear after all arguments. This behavior was 51 // removed n version 2 since it only works under specific conditions so we 52 // backport here by exposing it as an option for compatibility. 53 SkipArgReorder bool 54 // Boolean to hide built-in help command 55 HideHelp bool 56 // Boolean to hide this command from help or completion 57 Hidden bool 58 59 // Full name of command for help, defaults to full command name, including parent commands. 60 HelpName string 61 commandNamePath []string 62 63 // CustomHelpTemplate the text template for the command help topic. 64 // cli.go uses text/template to render templates. You can 65 // render custom help text by setting this variable. 66 CustomHelpTemplate string 67} 68 69type CommandsByName []Command 70 71func (c CommandsByName) Len() int { 72 return len(c) 73} 74 75func (c CommandsByName) Less(i, j int) bool { 76 return c[i].Name < c[j].Name 77} 78 79func (c CommandsByName) Swap(i, j int) { 80 c[i], c[j] = c[j], c[i] 81} 82 83// FullName returns the full name of the command. 84// For subcommands this ensures that parent commands are part of the command path 85func (c Command) FullName() string { 86 if c.commandNamePath == nil { 87 return c.Name 88 } 89 return strings.Join(c.commandNamePath, " ") 90} 91 92// Commands is a slice of Command 93type Commands []Command 94 95// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags 96func (c Command) Run(ctx *Context) (err error) { 97 if len(c.Subcommands) > 0 { 98 return c.startApp(ctx) 99 } 100 101 if !c.HideHelp && (HelpFlag != BoolFlag{}) { 102 // append help to flags 103 c.Flags = append( 104 c.Flags, 105 HelpFlag, 106 ) 107 } 108 109 set, err := flagSet(c.Name, c.Flags) 110 if err != nil { 111 return err 112 } 113 set.SetOutput(ioutil.Discard) 114 115 if c.SkipFlagParsing { 116 err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...)) 117 } else if !c.SkipArgReorder { 118 firstFlagIndex := -1 119 terminatorIndex := -1 120 for index, arg := range ctx.Args() { 121 if arg == "--" { 122 terminatorIndex = index 123 break 124 } else if arg == "-" { 125 // Do nothing. A dash alone is not really a flag. 126 continue 127 } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { 128 firstFlagIndex = index 129 } 130 } 131 132 if firstFlagIndex > -1 { 133 args := ctx.Args() 134 regularArgs := make([]string, len(args[1:firstFlagIndex])) 135 copy(regularArgs, args[1:firstFlagIndex]) 136 137 var flagArgs []string 138 if terminatorIndex > -1 { 139 flagArgs = args[firstFlagIndex:terminatorIndex] 140 regularArgs = append(regularArgs, args[terminatorIndex:]...) 141 } else { 142 flagArgs = args[firstFlagIndex:] 143 } 144 145 err = set.Parse(append(flagArgs, regularArgs...)) 146 } else { 147 err = set.Parse(ctx.Args().Tail()) 148 } 149 } else { 150 err = set.Parse(ctx.Args().Tail()) 151 } 152 153 nerr := normalizeFlags(c.Flags, set) 154 if nerr != nil { 155 fmt.Fprintln(ctx.App.Writer, nerr) 156 fmt.Fprintln(ctx.App.Writer) 157 ShowCommandHelp(ctx, c.Name) 158 return nerr 159 } 160 161 context := NewContext(ctx.App, set, ctx) 162 context.Command = c 163 if checkCommandCompletions(context, c.Name) { 164 return nil 165 } 166 167 if err != nil { 168 if c.OnUsageError != nil { 169 err := c.OnUsageError(context, err, false) 170 HandleExitCoder(err) 171 return err 172 } 173 fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error()) 174 fmt.Fprintln(context.App.Writer) 175 ShowCommandHelp(context, c.Name) 176 return err 177 } 178 179 if checkCommandHelp(context, c.Name) { 180 return nil 181 } 182 183 if c.After != nil { 184 defer func() { 185 afterErr := c.After(context) 186 if afterErr != nil { 187 HandleExitCoder(err) 188 if err != nil { 189 err = NewMultiError(err, afterErr) 190 } else { 191 err = afterErr 192 } 193 } 194 }() 195 } 196 197 if c.Before != nil { 198 err = c.Before(context) 199 if err != nil { 200 ShowCommandHelp(context, c.Name) 201 HandleExitCoder(err) 202 return err 203 } 204 } 205 206 if c.Action == nil { 207 c.Action = helpSubcommand.Action 208 } 209 210 err = HandleAction(c.Action, context) 211 212 if err != nil { 213 HandleExitCoder(err) 214 } 215 return err 216} 217 218// Names returns the names including short names and aliases. 219func (c Command) Names() []string { 220 names := []string{c.Name} 221 222 if c.ShortName != "" { 223 names = append(names, c.ShortName) 224 } 225 226 return append(names, c.Aliases...) 227} 228 229// HasName returns true if Command.Name or Command.ShortName matches given name 230func (c Command) HasName(name string) bool { 231 for _, n := range c.Names() { 232 if n == name { 233 return true 234 } 235 } 236 return false 237} 238 239func (c Command) startApp(ctx *Context) error { 240 app := NewApp() 241 app.Metadata = ctx.App.Metadata 242 // set the name and usage 243 app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) 244 if c.HelpName == "" { 245 app.HelpName = c.HelpName 246 } else { 247 app.HelpName = app.Name 248 } 249 250 app.Usage = c.Usage 251 app.Description = c.Description 252 app.ArgsUsage = c.ArgsUsage 253 254 // set CommandNotFound 255 app.CommandNotFound = ctx.App.CommandNotFound 256 app.CustomAppHelpTemplate = c.CustomHelpTemplate 257 258 // set the flags and commands 259 app.Commands = c.Subcommands 260 app.Flags = c.Flags 261 app.HideHelp = c.HideHelp 262 263 app.Version = ctx.App.Version 264 app.HideVersion = ctx.App.HideVersion 265 app.Compiled = ctx.App.Compiled 266 app.Author = ctx.App.Author 267 app.Email = ctx.App.Email 268 app.Writer = ctx.App.Writer 269 app.ErrWriter = ctx.App.ErrWriter 270 271 app.categories = CommandCategories{} 272 for _, command := range c.Subcommands { 273 app.categories = app.categories.AddCommand(command.Category, command) 274 } 275 276 sort.Sort(app.categories) 277 278 // bash completion 279 app.EnableBashCompletion = ctx.App.EnableBashCompletion 280 if c.BashComplete != nil { 281 app.BashComplete = c.BashComplete 282 } 283 284 // set the actions 285 app.Before = c.Before 286 app.After = c.After 287 if c.Action != nil { 288 app.Action = c.Action 289 } else { 290 app.Action = helpSubcommand.Action 291 } 292 app.OnUsageError = c.OnUsageError 293 294 for index, cc := range app.Commands { 295 app.Commands[index].commandNamePath = []string{c.Name, cc.Name} 296 } 297 298 return app.RunAsSubcommand(ctx) 299} 300 301// VisibleFlags returns a slice of the Flags with Hidden=false 302func (c Command) VisibleFlags() []Flag { 303 return visibleFlags(c.Flags) 304} 305