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 _ = ShowCommandHelp(context, c.Name) 154 context.App.handleExitCoder(context, err) 155 return err 156 } 157 } 158 159 if c.Action == nil { 160 c.Action = helpSubcommand.Action 161 } 162 163 context.Command = c 164 err = c.Action(context) 165 166 if err != nil { 167 context.App.handleExitCoder(context, err) 168 } 169 return err 170} 171 172func (c *Command) newFlagSet() (*flag.FlagSet, error) { 173 return flagSet(c.Name, c.Flags) 174} 175 176func (c *Command) useShortOptionHandling() bool { 177 return c.UseShortOptionHandling 178} 179 180func (c *Command) parseFlags(args Args, shellComplete bool) (*flag.FlagSet, error) { 181 set, err := c.newFlagSet() 182 if err != nil { 183 return nil, err 184 } 185 186 if c.SkipFlagParsing { 187 return set, set.Parse(append([]string{"--"}, args.Tail()...)) 188 } 189 190 err = parseIter(set, c, args.Tail(), shellComplete) 191 if err != nil { 192 return nil, err 193 } 194 195 err = normalizeFlags(c.Flags, set) 196 if err != nil { 197 return nil, err 198 } 199 200 return set, nil 201} 202 203// Names returns the names including short names and aliases. 204func (c *Command) Names() []string { 205 return append([]string{c.Name}, c.Aliases...) 206} 207 208// HasName returns true if Command.Name matches given name 209func (c *Command) HasName(name string) bool { 210 for _, n := range c.Names() { 211 if n == name { 212 return true 213 } 214 } 215 return false 216} 217 218func (c *Command) startApp(ctx *Context) error { 219 app := &App{ 220 Metadata: ctx.App.Metadata, 221 Name: fmt.Sprintf("%s %s", ctx.App.Name, c.Name), 222 } 223 224 if c.HelpName == "" { 225 app.HelpName = c.HelpName 226 } else { 227 app.HelpName = app.Name 228 } 229 230 app.Usage = c.Usage 231 app.Description = c.Description 232 app.ArgsUsage = c.ArgsUsage 233 234 // set CommandNotFound 235 app.CommandNotFound = ctx.App.CommandNotFound 236 app.CustomAppHelpTemplate = c.CustomHelpTemplate 237 238 // set the flags and commands 239 app.Commands = c.Subcommands 240 app.Flags = c.Flags 241 app.HideHelp = c.HideHelp 242 app.HideHelpCommand = c.HideHelpCommand 243 244 app.Version = ctx.App.Version 245 app.HideVersion = ctx.App.HideVersion 246 app.Compiled = ctx.App.Compiled 247 app.Writer = ctx.App.Writer 248 app.ErrWriter = ctx.App.ErrWriter 249 app.ExitErrHandler = ctx.App.ExitErrHandler 250 app.UseShortOptionHandling = ctx.App.UseShortOptionHandling 251 252 app.categories = newCommandCategories() 253 for _, command := range c.Subcommands { 254 app.categories.AddCommand(command.Category, command) 255 } 256 257 sort.Sort(app.categories.(*commandCategories)) 258 259 // bash completion 260 app.EnableBashCompletion = ctx.App.EnableBashCompletion 261 if c.BashComplete != nil { 262 app.BashComplete = c.BashComplete 263 } 264 265 // set the actions 266 app.Before = c.Before 267 app.After = c.After 268 if c.Action != nil { 269 app.Action = c.Action 270 } else { 271 app.Action = helpSubcommand.Action 272 } 273 app.OnUsageError = c.OnUsageError 274 275 for index, cc := range app.Commands { 276 app.Commands[index].commandNamePath = []string{c.Name, cc.Name} 277 } 278 279 return app.RunAsSubcommand(ctx) 280} 281 282// VisibleFlags returns a slice of the Flags with Hidden=false 283func (c *Command) VisibleFlags() []Flag { 284 return visibleFlags(c.Flags) 285} 286 287func (c *Command) appendFlag(fl Flag) { 288 if !hasFlag(c.Flags, fl) { 289 c.Flags = append(c.Flags, fl) 290 } 291} 292 293func hasCommand(commands []*Command, command *Command) bool { 294 for _, existing := range commands { 295 if command == existing { 296 return true 297 } 298 } 299 300 return false 301} 302