command.go 7.4 KB

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