command.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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. // Skip argument reordering which attempts to move flags before arguments,
  47. // but only works if all flags appear after all arguments. This behavior was
  48. // removed n version 2 since it only works under specific conditions so we
  49. // backport here by exposing it as an option for compatibility.
  50. SkipArgReorder bool
  51. // Boolean to hide built-in help command
  52. HideHelp bool
  53. // Boolean to hide this command from help or completion
  54. Hidden bool
  55. // Full name of command for help, defaults to full command name, including parent commands.
  56. HelpName string
  57. commandNamePath []string
  58. }
  59. // FullName returns the full name of the command.
  60. // For subcommands this ensures that parent commands are part of the command path
  61. func (c Command) FullName() string {
  62. if c.commandNamePath == nil {
  63. return c.Name
  64. }
  65. return strings.Join(c.commandNamePath, " ")
  66. }
  67. // Commands is a slice of Command
  68. type Commands []Command
  69. // Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
  70. func (c Command) Run(ctx *Context) (err error) {
  71. if len(c.Subcommands) > 0 {
  72. return c.startApp(ctx)
  73. }
  74. if !c.HideHelp && (HelpFlag != BoolFlag{}) {
  75. // append help to flags
  76. c.Flags = append(
  77. c.Flags,
  78. HelpFlag,
  79. )
  80. }
  81. set, err := flagSet(c.Name, c.Flags)
  82. if err != nil {
  83. return err
  84. }
  85. set.SetOutput(ioutil.Discard)
  86. if c.SkipFlagParsing {
  87. err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
  88. } else if !c.SkipArgReorder {
  89. firstFlagIndex := -1
  90. terminatorIndex := -1
  91. for index, arg := range ctx.Args() {
  92. if arg == "--" {
  93. terminatorIndex = index
  94. break
  95. } else if arg == "-" {
  96. // Do nothing. A dash alone is not really a flag.
  97. continue
  98. } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
  99. firstFlagIndex = index
  100. }
  101. }
  102. if firstFlagIndex > -1 {
  103. args := ctx.Args()
  104. regularArgs := make([]string, len(args[1:firstFlagIndex]))
  105. copy(regularArgs, args[1:firstFlagIndex])
  106. var flagArgs []string
  107. if terminatorIndex > -1 {
  108. flagArgs = args[firstFlagIndex:terminatorIndex]
  109. regularArgs = append(regularArgs, args[terminatorIndex:]...)
  110. } else {
  111. flagArgs = args[firstFlagIndex:]
  112. }
  113. err = set.Parse(append(flagArgs, regularArgs...))
  114. } else {
  115. err = set.Parse(ctx.Args().Tail())
  116. }
  117. } else {
  118. err = set.Parse(ctx.Args().Tail())
  119. }
  120. nerr := normalizeFlags(c.Flags, set)
  121. if nerr != nil {
  122. fmt.Fprintln(ctx.App.Writer, nerr)
  123. fmt.Fprintln(ctx.App.Writer)
  124. ShowCommandHelp(ctx, c.Name)
  125. return nerr
  126. }
  127. context := NewContext(ctx.App, set, ctx)
  128. if checkCommandCompletions(context, c.Name) {
  129. return nil
  130. }
  131. if err != nil {
  132. if c.OnUsageError != nil {
  133. err := c.OnUsageError(ctx, err, false)
  134. HandleExitCoder(err)
  135. return err
  136. }
  137. fmt.Fprintln(ctx.App.Writer, "Incorrect Usage:", err.Error())
  138. fmt.Fprintln(ctx.App.Writer)
  139. ShowCommandHelp(ctx, c.Name)
  140. return err
  141. }
  142. if checkCommandHelp(context, c.Name) {
  143. return nil
  144. }
  145. if c.After != nil {
  146. defer func() {
  147. afterErr := c.After(context)
  148. if afterErr != nil {
  149. HandleExitCoder(err)
  150. if err != nil {
  151. err = NewMultiError(err, afterErr)
  152. } else {
  153. err = afterErr
  154. }
  155. }
  156. }()
  157. }
  158. if c.Before != nil {
  159. err = c.Before(context)
  160. if err != nil {
  161. fmt.Fprintln(ctx.App.Writer, err)
  162. fmt.Fprintln(ctx.App.Writer)
  163. ShowCommandHelp(ctx, c.Name)
  164. HandleExitCoder(err)
  165. return err
  166. }
  167. }
  168. if c.Action == nil {
  169. c.Action = helpSubcommand.Action
  170. }
  171. context.Command = c
  172. err = HandleAction(c.Action, context)
  173. if err != nil {
  174. HandleExitCoder(err)
  175. }
  176. return err
  177. }
  178. // Names returns the names including short names and aliases.
  179. func (c Command) Names() []string {
  180. names := []string{c.Name}
  181. if c.ShortName != "" {
  182. names = append(names, c.ShortName)
  183. }
  184. return append(names, c.Aliases...)
  185. }
  186. // HasName returns true if Command.Name or Command.ShortName matches given name
  187. func (c Command) HasName(name string) bool {
  188. for _, n := range c.Names() {
  189. if n == name {
  190. return true
  191. }
  192. }
  193. return false
  194. }
  195. func (c Command) startApp(ctx *Context) error {
  196. app := NewApp()
  197. app.Metadata = ctx.App.Metadata
  198. // set the name and usage
  199. app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
  200. if c.HelpName == "" {
  201. app.HelpName = c.HelpName
  202. } else {
  203. app.HelpName = app.Name
  204. }
  205. if c.Description != "" {
  206. app.Usage = c.Description
  207. } else {
  208. app.Usage = c.Usage
  209. }
  210. // set CommandNotFound
  211. app.CommandNotFound = ctx.App.CommandNotFound
  212. // set the flags and commands
  213. app.Commands = c.Subcommands
  214. app.Flags = c.Flags
  215. app.HideHelp = c.HideHelp
  216. app.Version = ctx.App.Version
  217. app.HideVersion = ctx.App.HideVersion
  218. app.Compiled = ctx.App.Compiled
  219. app.Author = ctx.App.Author
  220. app.Email = ctx.App.Email
  221. app.Writer = ctx.App.Writer
  222. app.categories = CommandCategories{}
  223. for _, command := range c.Subcommands {
  224. app.categories = app.categories.AddCommand(command.Category, command)
  225. }
  226. sort.Sort(app.categories)
  227. // bash completion
  228. app.EnableBashCompletion = ctx.App.EnableBashCompletion
  229. if c.BashComplete != nil {
  230. app.BashComplete = c.BashComplete
  231. }
  232. // set the actions
  233. app.Before = c.Before
  234. app.After = c.After
  235. if c.Action != nil {
  236. app.Action = c.Action
  237. } else {
  238. app.Action = helpSubcommand.Action
  239. }
  240. for index, cc := range app.Commands {
  241. app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
  242. }
  243. return app.RunAsSubcommand(ctx)
  244. }
  245. // VisibleFlags returns a slice of the Flags with Hidden=false
  246. func (c Command) VisibleFlags() []Flag {
  247. return visibleFlags(c.Flags)
  248. }