You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

478 lines
11 KiB

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