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