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.
 
 
 

317 lines
7.6 KiB

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