1package cli 2 3import ( 4 "flag" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 "sort" 10 "time" 11) 12 13var ( 14 changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md" 15 appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL) 16 // unused variable. commented for now. will remove in future if agreed upon by everyone 17 //runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL) 18 19 contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you." 20 21 errInvalidActionType = NewExitError("ERROR invalid Action type. "+ 22 fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+ 23 fmt.Sprintf("See %s", appActionDeprecationURL), 2) 24) 25 26// App is the main structure of a cli application. It is recommended that 27// an app be created with the cli.NewApp() function 28type App struct { 29 // The name of the program. Defaults to path.Base(os.Args[0]) 30 Name string 31 // Full name of command for help, defaults to Name 32 HelpName string 33 // Description of the program. 34 Usage string 35 // Text to override the USAGE section of help 36 UsageText string 37 // Description of the program argument format. 38 ArgsUsage string 39 // Version of the program 40 Version string 41 // Description of the program 42 Description string 43 // List of commands to execute 44 Commands []Command 45 // List of flags to parse 46 Flags []Flag 47 // Boolean to enable bash completion commands 48 EnableBashCompletion bool 49 // Boolean to hide built-in help command 50 HideHelp bool 51 // Boolean to hide built-in version flag and the VERSION section of help 52 HideVersion bool 53 // Populate on app startup, only gettable through method Categories() 54 categories CommandCategories 55 // An action to execute when the bash-completion flag is set 56 BashComplete BashCompleteFunc 57 // An action to execute before any subcommands are run, but after the context is ready 58 // If a non-nil error is returned, no subcommands are run 59 Before BeforeFunc 60 // An action to execute after any subcommands are run, but after the subcommand has finished 61 // It is run even if Action() panics 62 After AfterFunc 63 64 // The action to execute when no subcommands are specified 65 // Expects a `cli.ActionFunc` but will accept the *deprecated* signature of `func(*cli.Context) {}` 66 // *Note*: support for the deprecated `Action` signature will be removed in a future version 67 Action interface{} 68 69 // Execute this function if the proper command cannot be found 70 CommandNotFound CommandNotFoundFunc 71 // Execute this function if an usage error occurs 72 OnUsageError OnUsageErrorFunc 73 // Compilation date 74 Compiled time.Time 75 // List of all authors who contributed 76 Authors []Author 77 // Copyright of the binary if any 78 Copyright string 79 // Name of Author (Note: Use App.Authors, this is deprecated) 80 Author string 81 // Email of Author (Note: Use App.Authors, this is deprecated) 82 Email string 83 // Writer writer to write output to 84 Writer io.Writer 85 // ErrWriter writes error output 86 ErrWriter io.Writer 87 // Execute this function to handle ExitErrors. If not provided, HandleExitCoder is provided to 88 // function as a default, so this is optional. 89 ExitErrHandler ExitErrHandlerFunc 90 // Other custom info 91 Metadata map[string]interface{} 92 // Carries a function which returns app specific info. 93 ExtraInfo func() map[string]string 94 // CustomAppHelpTemplate the text template for app help topic. 95 // cli.go uses text/template to render templates. You can 96 // render custom help text by setting this variable. 97 CustomAppHelpTemplate string 98 // Boolean to enable short-option handling so user can combine several 99 // single-character bool arguements into one 100 // i.e. foobar -o -v -> foobar -ov 101 UseShortOptionHandling bool 102 103 didSetup bool 104} 105 106// Tries to find out when this binary was compiled. 107// Returns the current time if it fails to find it. 108func compileTime() time.Time { 109 info, err := os.Stat(os.Args[0]) 110 if err != nil { 111 return time.Now() 112 } 113 return info.ModTime() 114} 115 116// NewApp creates a new cli Application with some reasonable defaults for Name, 117// Usage, Version and Action. 118func NewApp() *App { 119 return &App{ 120 Name: filepath.Base(os.Args[0]), 121 HelpName: filepath.Base(os.Args[0]), 122 Usage: "A new cli application", 123 UsageText: "", 124 BashComplete: DefaultAppComplete, 125 Action: helpCommand.Action, 126 Compiled: compileTime(), 127 Writer: os.Stdout, 128 } 129} 130 131// Setup runs initialization code to ensure all data structures are ready for 132// `Run` or inspection prior to `Run`. It is internally called by `Run`, but 133// will return early if setup has already happened. 134func (a *App) Setup() { 135 if a.didSetup { 136 return 137 } 138 139 a.didSetup = true 140 141 if a.Author != "" || a.Email != "" { 142 a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) 143 } 144 145 var newCmds []Command 146 for _, c := range a.Commands { 147 if c.HelpName == "" { 148 c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) 149 } 150 newCmds = append(newCmds, c) 151 } 152 a.Commands = newCmds 153 154 if a.Command(helpCommand.Name) == nil && !a.HideHelp { 155 a.Commands = append(a.Commands, helpCommand) 156 if (HelpFlag != BoolFlag{}) { 157 a.appendFlag(HelpFlag) 158 } 159 } 160 161 if a.Version == "" { 162 a.HideVersion = true 163 } 164 165 if !a.HideVersion { 166 a.appendFlag(VersionFlag) 167 } 168 169 a.categories = CommandCategories{} 170 for _, command := range a.Commands { 171 a.categories = a.categories.AddCommand(command.Category, command) 172 } 173 sort.Sort(a.categories) 174 175 if a.Metadata == nil { 176 a.Metadata = make(map[string]interface{}) 177 } 178 179 if a.Writer == nil { 180 a.Writer = os.Stdout 181 } 182} 183 184func (a *App) newFlagSet() (*flag.FlagSet, error) { 185 return flagSet(a.Name, a.Flags) 186} 187 188func (a *App) useShortOptionHandling() bool { 189 return a.UseShortOptionHandling 190} 191 192// Run is the entry point to the cli app. Parses the arguments slice and routes 193// to the proper flag/args combination 194func (a *App) Run(arguments []string) (err error) { 195 a.Setup() 196 197 // handle the completion flag separately from the flagset since 198 // completion could be attempted after a flag, but before its value was put 199 // on the command line. this causes the flagset to interpret the completion 200 // flag name as the value of the flag before it which is undesirable 201 // note that we can only do this because the shell autocomplete function 202 // always appends the completion flag at the end of the command 203 shellComplete, arguments := checkShellCompleteFlag(a, arguments) 204 205 set, err := a.newFlagSet() 206 if err != nil { 207 return err 208 } 209 210 err = parseIter(set, a, arguments[1:], shellComplete) 211 nerr := normalizeFlags(a.Flags, set) 212 context := NewContext(a, set, nil) 213 if nerr != nil { 214 _, _ = fmt.Fprintln(a.Writer, nerr) 215 _ = ShowAppHelp(context) 216 return nerr 217 } 218 context.shellComplete = shellComplete 219 220 if checkCompletions(context) { 221 return nil 222 } 223 224 if err != nil { 225 if a.OnUsageError != nil { 226 err := a.OnUsageError(context, err, false) 227 a.handleExitCoder(context, err) 228 return err 229 } 230 _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) 231 _ = ShowAppHelp(context) 232 return err 233 } 234 235 if !a.HideHelp && checkHelp(context) { 236 _ = ShowAppHelp(context) 237 return nil 238 } 239 240 if !a.HideVersion && checkVersion(context) { 241 ShowVersion(context) 242 return nil 243 } 244 245 cerr := checkRequiredFlags(a.Flags, context) 246 if cerr != nil { 247 _ = ShowAppHelp(context) 248 return cerr 249 } 250 251 if a.After != nil { 252 defer func() { 253 if afterErr := a.After(context); afterErr != nil { 254 if err != nil { 255 err = NewMultiError(err, afterErr) 256 } else { 257 err = afterErr 258 } 259 } 260 }() 261 } 262 263 if a.Before != nil { 264 beforeErr := a.Before(context) 265 if beforeErr != nil { 266 a.handleExitCoder(context, beforeErr) 267 err = beforeErr 268 return err 269 } 270 } 271 272 args := context.Args() 273 if args.Present() { 274 name := args.First() 275 c := a.Command(name) 276 if c != nil { 277 return c.Run(context) 278 } 279 } 280 281 if a.Action == nil { 282 a.Action = helpCommand.Action 283 } 284 285 // Run default Action 286 err = HandleAction(a.Action, context) 287 288 a.handleExitCoder(context, err) 289 return err 290} 291 292// RunAndExitOnError calls .Run() and exits non-zero if an error was returned 293// 294// Deprecated: instead you should return an error that fulfills cli.ExitCoder 295// to cli.App.Run. This will cause the application to exit with the given eror 296// code in the cli.ExitCoder 297func (a *App) RunAndExitOnError() { 298 if err := a.Run(os.Args); err != nil { 299 _, _ = fmt.Fprintln(a.errWriter(), err) 300 OsExiter(1) 301 } 302} 303 304// RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to 305// generate command-specific flags 306func (a *App) RunAsSubcommand(ctx *Context) (err error) { 307 // append help to commands 308 if len(a.Commands) > 0 { 309 if a.Command(helpCommand.Name) == nil && !a.HideHelp { 310 a.Commands = append(a.Commands, helpCommand) 311 if (HelpFlag != BoolFlag{}) { 312 a.appendFlag(HelpFlag) 313 } 314 } 315 } 316 317 newCmds := []Command{} 318 for _, c := range a.Commands { 319 if c.HelpName == "" { 320 c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) 321 } 322 newCmds = append(newCmds, c) 323 } 324 a.Commands = newCmds 325 326 set, err := a.newFlagSet() 327 if err != nil { 328 return err 329 } 330 331 err = parseIter(set, a, ctx.Args().Tail(), ctx.shellComplete) 332 nerr := normalizeFlags(a.Flags, set) 333 context := NewContext(a, set, ctx) 334 335 if nerr != nil { 336 _, _ = fmt.Fprintln(a.Writer, nerr) 337 _, _ = fmt.Fprintln(a.Writer) 338 if len(a.Commands) > 0 { 339 _ = ShowSubcommandHelp(context) 340 } else { 341 _ = ShowCommandHelp(ctx, context.Args().First()) 342 } 343 return nerr 344 } 345 346 if checkCompletions(context) { 347 return nil 348 } 349 350 if err != nil { 351 if a.OnUsageError != nil { 352 err = a.OnUsageError(context, err, true) 353 a.handleExitCoder(context, err) 354 return err 355 } 356 _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) 357 _ = ShowSubcommandHelp(context) 358 return err 359 } 360 361 if len(a.Commands) > 0 { 362 if checkSubcommandHelp(context) { 363 return nil 364 } 365 } else { 366 if checkCommandHelp(ctx, context.Args().First()) { 367 return nil 368 } 369 } 370 371 cerr := checkRequiredFlags(a.Flags, context) 372 if cerr != nil { 373 _ = ShowSubcommandHelp(context) 374 return cerr 375 } 376 377 if a.After != nil { 378 defer func() { 379 afterErr := a.After(context) 380 if afterErr != nil { 381 a.handleExitCoder(context, err) 382 if err != nil { 383 err = NewMultiError(err, afterErr) 384 } else { 385 err = afterErr 386 } 387 } 388 }() 389 } 390 391 if a.Before != nil { 392 beforeErr := a.Before(context) 393 if beforeErr != nil { 394 a.handleExitCoder(context, beforeErr) 395 err = beforeErr 396 return err 397 } 398 } 399 400 args := context.Args() 401 if args.Present() { 402 name := args.First() 403 c := a.Command(name) 404 if c != nil { 405 return c.Run(context) 406 } 407 } 408 409 // Run default Action 410 err = HandleAction(a.Action, context) 411 412 a.handleExitCoder(context, err) 413 return err 414} 415 416// Command returns the named command on App. Returns nil if the command does not exist 417func (a *App) Command(name string) *Command { 418 for _, c := range a.Commands { 419 if c.HasName(name) { 420 return &c 421 } 422 } 423 424 return nil 425} 426 427// Categories returns a slice containing all the categories with the commands they contain 428func (a *App) Categories() CommandCategories { 429 return a.categories 430} 431 432// VisibleCategories returns a slice of categories and commands that are 433// Hidden=false 434func (a *App) VisibleCategories() []*CommandCategory { 435 ret := []*CommandCategory{} 436 for _, category := range a.categories { 437 if visible := func() *CommandCategory { 438 for _, command := range category.Commands { 439 if !command.Hidden { 440 return category 441 } 442 } 443 return nil 444 }(); visible != nil { 445 ret = append(ret, visible) 446 } 447 } 448 return ret 449} 450 451// VisibleCommands returns a slice of the Commands with Hidden=false 452func (a *App) VisibleCommands() []Command { 453 var ret []Command 454 for _, command := range a.Commands { 455 if !command.Hidden { 456 ret = append(ret, command) 457 } 458 } 459 return ret 460} 461 462// VisibleFlags returns a slice of the Flags with Hidden=false 463func (a *App) VisibleFlags() []Flag { 464 return visibleFlags(a.Flags) 465} 466 467func (a *App) hasFlag(flag Flag) bool { 468 for _, f := range a.Flags { 469 if flag == f { 470 return true 471 } 472 } 473 474 return false 475} 476 477func (a *App) errWriter() io.Writer { 478 // When the app ErrWriter is nil use the package level one. 479 if a.ErrWriter == nil { 480 return ErrWriter 481 } 482 483 return a.ErrWriter 484} 485 486func (a *App) appendFlag(flag Flag) { 487 if !a.hasFlag(flag) { 488 a.Flags = append(a.Flags, flag) 489 } 490} 491 492func (a *App) handleExitCoder(context *Context, err error) { 493 if a.ExitErrHandler != nil { 494 a.ExitErrHandler(context, err) 495 } else { 496 HandleExitCoder(err) 497 } 498} 499 500// Author represents someone who has contributed to a cli project. 501type Author struct { 502 Name string // The Authors name 503 Email string // The Authors email 504} 505 506// String makes Author comply to the Stringer interface, to allow an easy print in the templating process 507func (a Author) String() string { 508 e := "" 509 if a.Email != "" { 510 e = " <" + a.Email + ">" 511 } 512 513 return fmt.Sprintf("%v%v", a.Name, e) 514} 515 516// HandleAction attempts to figure out which Action signature was used. If 517// it's an ActionFunc or a func with the legacy signature for Action, the func 518// is run! 519func HandleAction(action interface{}, context *Context) (err error) { 520 switch a := action.(type) { 521 case ActionFunc: 522 return a(context) 523 case func(*Context) error: 524 return a(context) 525 case func(*Context): // deprecated function signature 526 a(context) 527 return nil 528 } 529 530 return errInvalidActionType 531} 532