command.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. package cli
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "strings"
  6. )
  7. // Command is a subcommand for a cli.App.
  8. type Command struct {
  9. // The name of the command
  10. Name string
  11. // short name of the command. Typically one character (deprecated, use `Aliases`)
  12. ShortName string
  13. // A list of aliases for the command
  14. Aliases []string
  15. // A short description of the usage of this command
  16. Usage string
  17. // A longer explanation of how the command works
  18. Description string
  19. // A short description of the arguments of this command
  20. ArgsUsage string
  21. // The function to call when checking for bash command completions
  22. BashComplete func(context *Context)
  23. // An action to execute before any sub-subcommands are run, but after the context is ready
  24. // If a non-nil error is returned, no sub-subcommands are run
  25. Before func(context *Context) error
  26. // An action to execute after any subcommands are run, but after the subcommand has finished
  27. // It is run even if Action() panics
  28. After func(context *Context) error
  29. // The function to call when this command is invoked
  30. Action func(context *Context)
  31. // List of child commands
  32. Subcommands []Command
  33. // List of flags to parse
  34. Flags []Flag
  35. // Treat all flags as normal arguments if true
  36. SkipFlagParsing bool
  37. // Boolean to hide built-in help command
  38. HideHelp bool
  39. // Full name of command for help, defaults to full command name, including parent commands.
  40. HelpName string
  41. commandNamePath []string
  42. }
  43. // Returns the full name of the command.
  44. // For subcommands this ensures that parent commands are part of the command path
  45. func (c Command) FullName() string {
  46. if c.commandNamePath == nil {
  47. return c.Name
  48. }
  49. return strings.Join(c.commandNamePath, " ")
  50. }
  51. // Invokes the command given the context, parses ctx.Args() to generate command-specific flags
  52. func (c Command) Run(ctx *Context) error {
  53. if len(c.Subcommands) > 0 || c.Before != nil || c.After != nil {
  54. return c.startApp(ctx)
  55. }
  56. if !c.HideHelp && (HelpFlag != BoolFlag{}) {
  57. // append help to flags
  58. c.Flags = append(
  59. c.Flags,
  60. HelpFlag,
  61. )
  62. }
  63. if ctx.App.EnableBashCompletion {
  64. c.Flags = append(c.Flags, BashCompletionFlag)
  65. }
  66. set := flagSet(c.Name, c.Flags)
  67. set.SetOutput(ioutil.Discard)
  68. var err error
  69. if !c.SkipFlagParsing {
  70. firstFlagIndex := -1
  71. terminatorIndex := -1
  72. for index, arg := range ctx.Args() {
  73. if arg == "--" {
  74. terminatorIndex = index
  75. break
  76. } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
  77. firstFlagIndex = index
  78. }
  79. }
  80. if firstFlagIndex > -1 {
  81. args := ctx.Args()
  82. regularArgs := make([]string, len(args[1:firstFlagIndex]))
  83. copy(regularArgs, args[1:firstFlagIndex])
  84. var flagArgs []string
  85. if terminatorIndex > -1 {
  86. flagArgs = args[firstFlagIndex:terminatorIndex]
  87. regularArgs = append(regularArgs, args[terminatorIndex:]...)
  88. } else {
  89. flagArgs = args[firstFlagIndex:]
  90. }
  91. err = set.Parse(append(flagArgs, regularArgs...))
  92. } else {
  93. err = set.Parse(ctx.Args().Tail())
  94. }
  95. } else {
  96. if c.SkipFlagParsing {
  97. err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
  98. }
  99. }
  100. if err != nil {
  101. fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.")
  102. fmt.Fprintln(ctx.App.Writer)
  103. ShowCommandHelp(ctx, c.Name)
  104. return err
  105. }
  106. nerr := normalizeFlags(c.Flags, set)
  107. if nerr != nil {
  108. fmt.Fprintln(ctx.App.Writer, nerr)
  109. fmt.Fprintln(ctx.App.Writer)
  110. ShowCommandHelp(ctx, c.Name)
  111. return nerr
  112. }
  113. context := NewContext(ctx.App, set, ctx)
  114. if checkCommandCompletions(context, c.Name) {
  115. return nil
  116. }
  117. if checkCommandHelp(context, c.Name) {
  118. return nil
  119. }
  120. context.Command = c
  121. c.Action(context)
  122. return nil
  123. }
  124. func (c Command) Names() []string {
  125. names := []string{c.Name}
  126. if c.ShortName != "" {
  127. names = append(names, c.ShortName)
  128. }
  129. return append(names, c.Aliases...)
  130. }
  131. // Returns true if Command.Name or Command.ShortName matches given name
  132. func (c Command) HasName(name string) bool {
  133. for _, n := range c.Names() {
  134. if n == name {
  135. return true
  136. }
  137. }
  138. return false
  139. }
  140. func (c Command) startApp(ctx *Context) error {
  141. app := NewApp()
  142. // set the name and usage
  143. app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
  144. if c.HelpName == "" {
  145. app.HelpName = c.HelpName
  146. } else {
  147. app.HelpName = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
  148. }
  149. if c.Description != "" {
  150. app.Usage = c.Description
  151. } else {
  152. app.Usage = c.Usage
  153. }
  154. // set CommandNotFound
  155. app.CommandNotFound = ctx.App.CommandNotFound
  156. // set the flags and commands
  157. app.Commands = c.Subcommands
  158. app.Flags = c.Flags
  159. app.HideHelp = c.HideHelp
  160. app.Version = ctx.App.Version
  161. app.HideVersion = ctx.App.HideVersion
  162. app.Compiled = ctx.App.Compiled
  163. app.Author = ctx.App.Author
  164. app.Email = ctx.App.Email
  165. app.Writer = ctx.App.Writer
  166. // bash completion
  167. app.EnableBashCompletion = ctx.App.EnableBashCompletion
  168. if c.BashComplete != nil {
  169. app.BashComplete = c.BashComplete
  170. }
  171. // set the actions
  172. app.Before = c.Before
  173. app.After = c.After
  174. if c.Action != nil {
  175. app.Action = c.Action
  176. } else {
  177. app.Action = helpSubcommand.Action
  178. }
  179. var newCmds []Command
  180. for _, cc := range app.Commands {
  181. cc.commandNamePath = []string{c.Name, cc.Name}
  182. newCmds = append(newCmds, cc)
  183. }
  184. app.Commands = newCmds
  185. return app.RunAsSubcommand(ctx)
  186. }