command.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. package cli
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "sort"
  6. "strings"
  7. )
  8. // Command is a subcommand for a cli.App.
  9. type Command struct {
  10. // The name of the command
  11. Name string
  12. // short name of the command. Typically one character (deprecated, use `Aliases`)
  13. ShortName 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 interface{}
  36. // TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
  37. // of deprecation period has passed, maybe?
  38. // Execute this function if a usage error occurs.
  39. OnUsageError OnUsageErrorFunc
  40. // List of child commands
  41. Subcommands Commands
  42. // List of flags to parse
  43. Flags []Flag
  44. // Treat all flags as normal arguments if true
  45. SkipFlagParsing bool
  46. // Boolean to hide built-in help command
  47. HideHelp bool
  48. // Boolean to hide this command from help or completion
  49. Hidden bool
  50. // Full name of command for help, defaults to full command name, including parent commands.
  51. HelpName string
  52. commandNamePath []string
  53. }
  54. // FullName returns the full name of the command.
  55. // For subcommands this ensures that parent commands are part of the command path
  56. func (c Command) FullName() string {
  57. if c.commandNamePath == nil {
  58. return c.Name
  59. }
  60. return strings.Join(c.commandNamePath, " ")
  61. }
  62. // Commands is a slice of Command
  63. type Commands []Command
  64. // Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
  65. func (c Command) Run(ctx *Context) (err error) {
  66. if len(c.Subcommands) > 0 {
  67. return c.startApp(ctx)
  68. }
  69. if !c.HideHelp && (HelpFlag != BoolFlag{}) {
  70. // append help to flags
  71. c.Flags = append(
  72. c.Flags,
  73. HelpFlag,
  74. )
  75. }
  76. if ctx.App.EnableBashCompletion {
  77. c.Flags = append(c.Flags, BashCompletionFlag)
  78. }
  79. set := flagSet(c.Name, c.Flags)
  80. set.SetOutput(ioutil.Discard)
  81. if !c.SkipFlagParsing {
  82. firstFlagIndex := -1
  83. terminatorIndex := -1
  84. for index, arg := range ctx.Args() {
  85. if arg == "--" {
  86. terminatorIndex = index
  87. break
  88. } else if arg == "-" {
  89. // Do nothing. A dash alone is not really a flag.
  90. continue
  91. } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
  92. firstFlagIndex = index
  93. }
  94. }
  95. if firstFlagIndex > -1 {
  96. args := ctx.Args()
  97. regularArgs := make([]string, len(args[1:firstFlagIndex]))
  98. copy(regularArgs, args[1:firstFlagIndex])
  99. var flagArgs []string
  100. if terminatorIndex > -1 {
  101. flagArgs = args[firstFlagIndex:terminatorIndex]
  102. regularArgs = append(regularArgs, args[terminatorIndex:]...)
  103. } else {
  104. flagArgs = args[firstFlagIndex:]
  105. }
  106. err = set.Parse(append(flagArgs, regularArgs...))
  107. } else {
  108. err = set.Parse(ctx.Args().Tail())
  109. }
  110. } else {
  111. if c.SkipFlagParsing {
  112. err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
  113. }
  114. }
  115. if err != nil {
  116. if c.OnUsageError != nil {
  117. err := c.OnUsageError(ctx, err, false)
  118. HandleExitCoder(err)
  119. return err
  120. }
  121. fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.")
  122. fmt.Fprintln(ctx.App.Writer)
  123. ShowCommandHelp(ctx, c.Name)
  124. return err
  125. }
  126. nerr := normalizeFlags(c.Flags, set)
  127. if nerr != nil {
  128. fmt.Fprintln(ctx.App.Writer, nerr)
  129. fmt.Fprintln(ctx.App.Writer)
  130. ShowCommandHelp(ctx, c.Name)
  131. return nerr
  132. }
  133. context := NewContext(ctx.App, set, ctx)
  134. if checkCommandCompletions(context, c.Name) {
  135. return nil
  136. }
  137. if checkCommandHelp(context, c.Name) {
  138. return nil
  139. }
  140. if c.After != nil {
  141. defer func() {
  142. afterErr := c.After(context)
  143. if afterErr != nil {
  144. HandleExitCoder(err)
  145. if err != nil {
  146. err = NewMultiError(err, afterErr)
  147. } else {
  148. err = afterErr
  149. }
  150. }
  151. }()
  152. }
  153. if c.Before != nil {
  154. err = c.Before(context)
  155. if err != nil {
  156. fmt.Fprintln(ctx.App.Writer, err)
  157. fmt.Fprintln(ctx.App.Writer)
  158. ShowCommandHelp(ctx, c.Name)
  159. HandleExitCoder(err)
  160. return err
  161. }
  162. }
  163. context.Command = c
  164. err = HandleAction(c.Action, context)
  165. if err != nil {
  166. HandleExitCoder(err)
  167. }
  168. return err
  169. }
  170. // Names returns the names including short names and aliases.
  171. func (c Command) Names() []string {
  172. names := []string{c.Name}
  173. if c.ShortName != "" {
  174. names = append(names, c.ShortName)
  175. }
  176. return append(names, c.Aliases...)
  177. }
  178. // HasName returns true if Command.Name or Command.ShortName matches given name
  179. func (c Command) HasName(name string) bool {
  180. for _, n := range c.Names() {
  181. if n == name {
  182. return true
  183. }
  184. }
  185. return false
  186. }
  187. func (c Command) startApp(ctx *Context) error {
  188. app := NewApp()
  189. app.Metadata = ctx.App.Metadata
  190. // set the name and usage
  191. app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
  192. if c.HelpName == "" {
  193. app.HelpName = c.HelpName
  194. } else {
  195. app.HelpName = app.Name
  196. }
  197. if c.Description != "" {
  198. app.Usage = c.Description
  199. } else {
  200. app.Usage = c.Usage
  201. }
  202. // set CommandNotFound
  203. app.CommandNotFound = ctx.App.CommandNotFound
  204. // set the flags and commands
  205. app.Commands = c.Subcommands
  206. app.Flags = c.Flags
  207. app.HideHelp = c.HideHelp
  208. app.Version = ctx.App.Version
  209. app.HideVersion = ctx.App.HideVersion
  210. app.Compiled = ctx.App.Compiled
  211. app.Author = ctx.App.Author
  212. app.Email = ctx.App.Email
  213. app.Writer = ctx.App.Writer
  214. app.categories = CommandCategories{}
  215. for _, command := range c.Subcommands {
  216. app.categories = app.categories.AddCommand(command.Category, command)
  217. }
  218. sort.Sort(app.categories)
  219. // bash completion
  220. app.EnableBashCompletion = ctx.App.EnableBashCompletion
  221. if c.BashComplete != nil {
  222. app.BashComplete = c.BashComplete
  223. }
  224. // set the actions
  225. app.Before = c.Before
  226. app.After = c.After
  227. if c.Action != nil {
  228. app.Action = c.Action
  229. } else {
  230. app.Action = helpSubcommand.Action
  231. }
  232. for index, cc := range app.Commands {
  233. app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
  234. }
  235. return app.RunAsSubcommand(ctx)
  236. }
  237. // VisibleFlags returns a slice of the Flags with Hidden=false
  238. func (c Command) VisibleFlags() []Flag {
  239. return visibleFlags(c.Flags)
  240. }