command.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. package cli
  2. import (
  3. "flag"
  4. "fmt"
  5. "io/ioutil"
  6. "sort"
  7. "strings"
  8. )
  9. // Command is a subcommand for a cli.App.
  10. type Command struct {
  11. // The name of the command
  12. Name string
  13. // short name of the command. Typically one character (deprecated, use `Aliases`)
  14. ShortName string
  15. // A list of aliases for the command
  16. Aliases []string
  17. // A short description of the usage of this command
  18. Usage string
  19. // Custom text to show on USAGE section of help
  20. UsageText string
  21. // A longer explanation of how the command works
  22. Description string
  23. // A short description of the arguments of this command
  24. ArgsUsage string
  25. // The category the command is part of
  26. Category string
  27. // The function to call when checking for bash command completions
  28. BashComplete BashCompleteFunc
  29. // An action to execute before any sub-subcommands are run, but after the context is ready
  30. // If a non-nil error is returned, no sub-subcommands are run
  31. Before BeforeFunc
  32. // An action to execute after any subcommands are run, but after the subcommand has finished
  33. // It is run even if Action() panics
  34. After AfterFunc
  35. // The function to call when this command is invoked
  36. Action interface{}
  37. // TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
  38. // of deprecation period has passed, maybe?
  39. // Execute this function if a usage error occurs.
  40. OnUsageError OnUsageErrorFunc
  41. // List of child commands
  42. Subcommands Commands
  43. // List of flags to parse
  44. Flags []Flag
  45. // Treat all flags as normal arguments if true
  46. SkipFlagParsing bool
  47. // Skip argument reordering which attempts to move flags before arguments,
  48. // but only works if all flags appear after all arguments. This behavior was
  49. // removed n version 2 since it only works under specific conditions so we
  50. // backport here by exposing it as an option for compatibility.
  51. SkipArgReorder bool
  52. // Boolean to hide built-in help command
  53. HideHelp bool
  54. // Boolean to hide this command from help or completion
  55. Hidden bool
  56. // Boolean to enable short-option handling so user can combine several
  57. // single-character bool arguements into one
  58. // i.e. foobar -o -v -> foobar -ov
  59. UseShortOptionHandling bool
  60. // Full name of command for help, defaults to full command name, including parent commands.
  61. HelpName string
  62. commandNamePath []string
  63. // CustomHelpTemplate the text template for the command help topic.
  64. // cli.go uses text/template to render templates. You can
  65. // render custom help text by setting this variable.
  66. CustomHelpTemplate string
  67. }
  68. type CommandsByName []Command
  69. func (c CommandsByName) Len() int {
  70. return len(c)
  71. }
  72. func (c CommandsByName) Less(i, j int) bool {
  73. return lexicographicLess(c[i].Name, c[j].Name)
  74. }
  75. func (c CommandsByName) Swap(i, j int) {
  76. c[i], c[j] = c[j], c[i]
  77. }
  78. // FullName returns the full name of the command.
  79. // For subcommands this ensures that parent commands are part of the command path
  80. func (c Command) FullName() string {
  81. if c.commandNamePath == nil {
  82. return c.Name
  83. }
  84. return strings.Join(c.commandNamePath, " ")
  85. }
  86. // Commands is a slice of Command
  87. type Commands []Command
  88. // Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
  89. func (c Command) Run(ctx *Context) (err error) {
  90. if len(c.Subcommands) > 0 {
  91. return c.startApp(ctx)
  92. }
  93. if !c.HideHelp && (HelpFlag != BoolFlag{}) {
  94. // append help to flags
  95. c.Flags = append(
  96. c.Flags,
  97. HelpFlag,
  98. )
  99. }
  100. set, err := c.parseFlags(ctx.Args().Tail())
  101. context := NewContext(ctx.App, set, ctx)
  102. context.Command = c
  103. if checkCommandCompletions(context, c.Name) {
  104. return nil
  105. }
  106. if err != nil {
  107. if c.OnUsageError != nil {
  108. err := c.OnUsageError(context, err, false)
  109. context.App.handleExitCoder(context, err)
  110. return err
  111. }
  112. fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
  113. fmt.Fprintln(context.App.Writer)
  114. ShowCommandHelp(context, c.Name)
  115. return err
  116. }
  117. if checkCommandHelp(context, c.Name) {
  118. return nil
  119. }
  120. if c.After != nil {
  121. defer func() {
  122. afterErr := c.After(context)
  123. if afterErr != nil {
  124. context.App.handleExitCoder(context, err)
  125. if err != nil {
  126. err = NewMultiError(err, afterErr)
  127. } else {
  128. err = afterErr
  129. }
  130. }
  131. }()
  132. }
  133. if c.Before != nil {
  134. err = c.Before(context)
  135. if err != nil {
  136. ShowCommandHelp(context, c.Name)
  137. context.App.handleExitCoder(context, err)
  138. return err
  139. }
  140. }
  141. if c.Action == nil {
  142. c.Action = helpSubcommand.Action
  143. }
  144. err = HandleAction(c.Action, context)
  145. if err != nil {
  146. context.App.handleExitCoder(context, err)
  147. }
  148. return err
  149. }
  150. func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) {
  151. set, err := flagSet(c.Name, c.Flags)
  152. if err != nil {
  153. return nil, err
  154. }
  155. set.SetOutput(ioutil.Discard)
  156. if c.SkipFlagParsing {
  157. return set, set.Parse(append([]string{"--"}, args...))
  158. }
  159. if c.UseShortOptionHandling {
  160. args = translateShortOptions(args)
  161. }
  162. if !c.SkipArgReorder {
  163. args = reorderArgs(args)
  164. }
  165. err = set.Parse(args)
  166. if err != nil {
  167. return nil, err
  168. }
  169. err = normalizeFlags(c.Flags, set)
  170. if err != nil {
  171. return nil, err
  172. }
  173. return set, nil
  174. }
  175. // reorderArgs moves all flags before arguments as this is what flag expects
  176. func reorderArgs(args []string) []string {
  177. var nonflags, flags []string
  178. readFlagValue := false
  179. for i, arg := range args {
  180. if arg == "--" {
  181. nonflags = append(nonflags, args[i:]...)
  182. break
  183. }
  184. if readFlagValue && !strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") {
  185. readFlagValue = false
  186. flags = append(flags, arg)
  187. continue
  188. }
  189. readFlagValue = false
  190. if arg != "-" && strings.HasPrefix(arg, "-") {
  191. flags = append(flags, arg)
  192. readFlagValue = !strings.Contains(arg, "=")
  193. } else {
  194. nonflags = append(nonflags, arg)
  195. }
  196. }
  197. return append(flags, nonflags...)
  198. }
  199. func translateShortOptions(flagArgs Args) []string {
  200. // separate combined flags
  201. var flagArgsSeparated []string
  202. for _, flagArg := range flagArgs {
  203. if strings.HasPrefix(flagArg, "-") && strings.HasPrefix(flagArg, "--") == false && len(flagArg) > 2 {
  204. for _, flagChar := range flagArg[1:] {
  205. flagArgsSeparated = append(flagArgsSeparated, "-"+string(flagChar))
  206. }
  207. } else {
  208. flagArgsSeparated = append(flagArgsSeparated, flagArg)
  209. }
  210. }
  211. return flagArgsSeparated
  212. }
  213. // Names returns the names including short names and aliases.
  214. func (c Command) Names() []string {
  215. names := []string{c.Name}
  216. if c.ShortName != "" {
  217. names = append(names, c.ShortName)
  218. }
  219. return append(names, c.Aliases...)
  220. }
  221. // HasName returns true if Command.Name or Command.ShortName matches given name
  222. func (c Command) HasName(name string) bool {
  223. for _, n := range c.Names() {
  224. if n == name {
  225. return true
  226. }
  227. }
  228. return false
  229. }
  230. func (c Command) startApp(ctx *Context) error {
  231. app := NewApp()
  232. app.Metadata = ctx.App.Metadata
  233. // set the name and usage
  234. app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
  235. if c.HelpName == "" {
  236. app.HelpName = c.HelpName
  237. } else {
  238. app.HelpName = app.Name
  239. }
  240. app.Usage = c.Usage
  241. app.Description = c.Description
  242. app.ArgsUsage = c.ArgsUsage
  243. // set CommandNotFound
  244. app.CommandNotFound = ctx.App.CommandNotFound
  245. app.CustomAppHelpTemplate = c.CustomHelpTemplate
  246. // set the flags and commands
  247. app.Commands = c.Subcommands
  248. app.Flags = c.Flags
  249. app.HideHelp = c.HideHelp
  250. app.Version = ctx.App.Version
  251. app.HideVersion = ctx.App.HideVersion
  252. app.Compiled = ctx.App.Compiled
  253. app.Author = ctx.App.Author
  254. app.Email = ctx.App.Email
  255. app.Writer = ctx.App.Writer
  256. app.ErrWriter = ctx.App.ErrWriter
  257. app.categories = CommandCategories{}
  258. for _, command := range c.Subcommands {
  259. app.categories = app.categories.AddCommand(command.Category, command)
  260. }
  261. sort.Sort(app.categories)
  262. // bash completion
  263. app.EnableBashCompletion = ctx.App.EnableBashCompletion
  264. if c.BashComplete != nil {
  265. app.BashComplete = c.BashComplete
  266. }
  267. // set the actions
  268. app.Before = c.Before
  269. app.After = c.After
  270. if c.Action != nil {
  271. app.Action = c.Action
  272. } else {
  273. app.Action = helpSubcommand.Action
  274. }
  275. app.OnUsageError = c.OnUsageError
  276. for index, cc := range app.Commands {
  277. app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
  278. }
  279. return app.RunAsSubcommand(ctx)
  280. }
  281. // VisibleFlags returns a slice of the Flags with Hidden=false
  282. func (c Command) VisibleFlags() []Flag {
  283. return visibleFlags(c.Flags)
  284. }