1package cli 2 3import ( 4 "flag" 5 "fmt" 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 // A list of aliases for the command 15 Aliases []string 16 // A short description of the usage of this command 17 Usage string 18 // Custom text to show on USAGE section of help 19 UsageText string 20 // A longer explanation of how the command works 21 Description string 22 // A short description of the arguments of this command 23 ArgsUsage string 24 // The category the command is part of 25 Category string 26 // The function to call when checking for bash command completions 27 BashComplete BashCompleteFunc 28 // An action to execute before any sub-subcommands are run, but after the context is ready 29 // If a non-nil error is returned, no sub-subcommands are run 30 Before BeforeFunc 31 // An action to execute after any subcommands are run, but after the subcommand has finished 32 // It is run even if Action() panics 33 After AfterFunc 34 // The function to call when this command is invoked 35 Action ActionFunc 36 // Execute this function if a usage error occurs. 37 OnUsageError OnUsageErrorFunc 38 // List of child commands 39 Subcommands []*Command 40 // List of flags to parse 41 Flags []Flag 42 // Treat all flags as normal arguments if true 43 SkipFlagParsing bool 44 // Boolean to hide built-in help command and help flag 45 HideHelp bool 46 // Boolean to hide built-in help command but keep help flag 47 // Ignored if HideHelp is true. 48 HideHelpCommand bool 49 // Boolean to hide this command from help or completion 50 Hidden bool 51 // Boolean to enable short-option handling so user can combine several 52 // single-character bool arguments into one 53 // i.e. foobar -o -v -> foobar -ov 54 UseShortOptionHandling bool 55 56 // Full name of command for help, defaults to full command name, including parent commands. 57 HelpName string 58 commandNamePath []string 59 60 // CustomHelpTemplate the text template for the command help topic. 61 // cli.go uses text/template to render templates. You can 62 // render custom help text by setting this variable. 63 CustomHelpTemplate string 64} 65 66type Commands []*Command 67 68type CommandsByName []*Command 69 70func (c CommandsByName) Len() int { 71 return len(c) 72} 73 74func (c CommandsByName) Less(i, j int) bool { 75 return lexicographicLess(c[i].Name, c[j].Name) 76} 77 78func (c CommandsByName) Swap(i, j int) { 79 c[i], c[j] = c[j], c[i] 80} 81 82// FullName returns the full name of the command. 83// For subcommands this ensures that parent commands are part of the command path 84func (c *Command) FullName() string { 85 if c.commandNamePath == nil { 86 return c.Name 87 } 88 return strings.Join(c.commandNamePath, " ") 89} 90 91// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags 92func (c *Command) Run(ctx *Context) (err error) { 93 if len(c.Subcommands) > 0 { 94 return c.startApp(ctx) 95 } 96 97 if !c.HideHelp && HelpFlag != nil { 98 // append help to flags 99 c.appendFlag(HelpFlag) 100 } 101 102 if ctx.App.UseShortOptionHandling { 103 c.UseShortOptionHandling = true 104 } 105 106 set, err := c.parseFlags(ctx.Args(), ctx.shellComplete) 107 108 context := NewContext(ctx.App, set, ctx) 109 context.Command = c 110 if checkCommandCompletions(context, c.Name) { 111 return nil 112 } 113 114 if err != nil { 115 if c.OnUsageError != nil { 116 err = c.OnUsageError(context, err, false) 117 context.App.handleExitCoder(context, err) 118 return err 119 } 120 _, _ = fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error()) 121 _, _ = fmt.Fprintln(context.App.Writer) 122 _ = ShowCommandHelp(context, c.Name) 123 return err 124 } 125 126 if checkCommandHelp(context, c.Name) { 127 return nil 128 } 129 130 cerr := checkRequiredFlags(c.Flags, context) 131 if cerr != nil { 132 _ = ShowCommandHelp(context, c.Name) 133 return cerr 134 } 135 136 if c.After != nil { 137 defer func() { 138 afterErr := c.After(context) 139 if afterErr != nil { 140 context.App.handleExitCoder(context, err) 141 if err != nil { 142 err = newMultiError(err, afterErr) 143 } else { 144 err = afterErr 145 } 146 } 147 }() 148 } 149 150 if c.Before != nil { 151 err = c.Before(context) 152 if err != nil { 153 context.App.handleExitCoder(context, err) 154 return err 155 } 156 } 157 158 if c.Action == nil { 159 c.Action = helpSubcommand.Action 160 } 161 162 context.Command = c 163 err = c.Action(context) 164 165 if err != nil { 166 context.App.handleExitCoder(context, err) 167 } 168 return err 169} 170 171func (c *Command) newFlagSet() (*flag.FlagSet, error) { 172 return flagSet(c.Name, c.Flags) 173} 174 175func (c *Command) useShortOptionHandling() bool { 176 return c.UseShortOptionHandling 177} 178 179func (c *Command) parseFlags(args Args, shellComplete bool) (*flag.FlagSet, error) { 180 set, err := c.newFlagSet() 181 if err != nil { 182 return nil, err 183 } 184 185 if c.SkipFlagParsing { 186 return set, set.Parse(append([]string{"--"}, args.Tail()...)) 187 } 188 189 err = parseIter(set, c, args.Tail(), shellComplete) 190 if err != nil { 191 return nil, err 192 } 193 194 err = normalizeFlags(c.Flags, set) 195 if err != nil { 196 return nil, err 197 } 198 199 return set, nil 200} 201 202// Names returns the names including short names and aliases. 203func (c *Command) Names() []string { 204 return append([]string{c.Name}, c.Aliases...) 205} 206 207// HasName returns true if Command.Name matches given name 208func (c *Command) HasName(name string) bool { 209 for _, n := range c.Names() { 210 if n == name { 211 return true 212 } 213 } 214 return false 215} 216 217func (c *Command) startApp(ctx *Context) error { 218 app := &App{ 219 Metadata: ctx.App.Metadata, 220 Name: fmt.Sprintf("%s %s", ctx.App.Name, c.Name), 221 } 222 223 if c.HelpName == "" { 224 app.HelpName = c.HelpName 225 } else { 226 app.HelpName = app.Name 227 } 228 229 app.Usage = c.Usage 230 app.Description = c.Description 231 app.ArgsUsage = c.ArgsUsage 232 233 // set CommandNotFound 234 app.CommandNotFound = ctx.App.CommandNotFound 235 app.CustomAppHelpTemplate = c.CustomHelpTemplate 236 237 // set the flags and commands 238 app.Commands = c.Subcommands 239 app.Flags = c.Flags 240 app.HideHelp = c.HideHelp 241 app.HideHelpCommand = c.HideHelpCommand 242 243 app.Version = ctx.App.Version 244 app.HideVersion = true 245 app.Compiled = ctx.App.Compiled 246 app.Writer = ctx.App.Writer 247 app.ErrWriter = ctx.App.ErrWriter 248 app.ExitErrHandler = ctx.App.ExitErrHandler 249 app.UseShortOptionHandling = ctx.App.UseShortOptionHandling 250 251 app.categories = newCommandCategories() 252 for _, command := range c.Subcommands { 253 app.categories.AddCommand(command.Category, command) 254 } 255 256 sort.Sort(app.categories.(*commandCategories)) 257 258 // bash completion 259 app.EnableBashCompletion = ctx.App.EnableBashCompletion 260 if c.BashComplete != nil { 261 app.BashComplete = c.BashComplete 262 } 263 264 // set the actions 265 app.Before = c.Before 266 app.After = c.After 267 if c.Action != nil { 268 app.Action = c.Action 269 } else { 270 app.Action = helpSubcommand.Action 271 } 272 app.OnUsageError = c.OnUsageError 273 274 for index, cc := range app.Commands { 275 app.Commands[index].commandNamePath = []string{c.Name, cc.Name} 276 } 277 278 return app.RunAsSubcommand(ctx) 279} 280 281// VisibleFlags returns a slice of the Flags with Hidden=false 282func (c *Command) VisibleFlags() []Flag { 283 return visibleFlags(c.Flags) 284} 285 286func (c *Command) appendFlag(fl Flag) { 287 if !hasFlag(c.Flags, fl) { 288 c.Flags = append(c.Flags, fl) 289 } 290} 291 292func hasCommand(commands []*Command, command *Command) bool { 293 for _, existing := range commands { 294 if command == existing { 295 return true 296 } 297 } 298 299 return false 300} 301