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.

465 lines
10 KiB

  1. package flags
  2. import (
  3. "reflect"
  4. "sort"
  5. "strconv"
  6. "strings"
  7. )
  8. // Command represents an application command. Commands can be added to the
  9. // parser (which itself is a command) and are selected/executed when its name
  10. // is specified on the command line. The Command type embeds a Group and
  11. // therefore also carries a set of command specific options.
  12. type Command struct {
  13. // Embedded, see Group for more information
  14. *Group
  15. // The name by which the command can be invoked
  16. Name string
  17. // The active sub command (set by parsing) or nil
  18. Active *Command
  19. // Whether subcommands are optional
  20. SubcommandsOptional bool
  21. // Aliases for the command
  22. Aliases []string
  23. // Whether positional arguments are required
  24. ArgsRequired bool
  25. commands []*Command
  26. hasBuiltinHelpGroup bool
  27. args []*Arg
  28. }
  29. // Commander is an interface which can be implemented by any command added in
  30. // the options. When implemented, the Execute method will be called for the last
  31. // specified (sub)command providing the remaining command line arguments.
  32. type Commander interface {
  33. // Execute will be called for the last active (sub)command. The
  34. // args argument contains the remaining command line arguments. The
  35. // error that Execute returns will be eventually passed out of the
  36. // Parse method of the Parser.
  37. Execute(args []string) error
  38. }
  39. // Usage is an interface which can be implemented to show a custom usage string
  40. // in the help message shown for a command.
  41. type Usage interface {
  42. // Usage is called for commands to allow customized printing of command
  43. // usage in the generated help message.
  44. Usage() string
  45. }
  46. type lookup struct {
  47. shortNames map[string]*Option
  48. longNames map[string]*Option
  49. commands map[string]*Command
  50. }
  51. // AddCommand adds a new command to the parser with the given name and data. The
  52. // data needs to be a pointer to a struct from which the fields indicate which
  53. // options are in the command. The provided data can implement the Command and
  54. // Usage interfaces.
  55. func (c *Command) AddCommand(command string, shortDescription string, longDescription string, data interface{}) (*Command, error) {
  56. cmd := newCommand(command, shortDescription, longDescription, data)
  57. cmd.parent = c
  58. if err := cmd.scan(); err != nil {
  59. return nil, err
  60. }
  61. c.commands = append(c.commands, cmd)
  62. return cmd, nil
  63. }
  64. // AddGroup adds a new group to the command with the given name and data. The
  65. // data needs to be a pointer to a struct from which the fields indicate which
  66. // options are in the group.
  67. func (c *Command) AddGroup(shortDescription string, longDescription string, data interface{}) (*Group, error) {
  68. group := newGroup(shortDescription, longDescription, data)
  69. group.parent = c
  70. if err := group.scanType(c.scanSubcommandHandler(group)); err != nil {
  71. return nil, err
  72. }
  73. c.groups = append(c.groups, group)
  74. return group, nil
  75. }
  76. // Commands returns a list of subcommands of this command.
  77. func (c *Command) Commands() []*Command {
  78. return c.commands
  79. }
  80. // Find locates the subcommand with the given name and returns it. If no such
  81. // command can be found Find will return nil.
  82. func (c *Command) Find(name string) *Command {
  83. for _, cc := range c.commands {
  84. if cc.match(name) {
  85. return cc
  86. }
  87. }
  88. return nil
  89. }
  90. // FindOptionByLongName finds an option that is part of the command, or any of
  91. // its parent commands, by matching its long name (including the option
  92. // namespace).
  93. func (c *Command) FindOptionByLongName(longName string) (option *Option) {
  94. for option == nil && c != nil {
  95. option = c.Group.FindOptionByLongName(longName)
  96. c, _ = c.parent.(*Command)
  97. }
  98. return option
  99. }
  100. // FindOptionByShortName finds an option that is part of the command, or any of
  101. // its parent commands, by matching its long name (including the option
  102. // namespace).
  103. func (c *Command) FindOptionByShortName(shortName rune) (option *Option) {
  104. for option == nil && c != nil {
  105. option = c.Group.FindOptionByShortName(shortName)
  106. c, _ = c.parent.(*Command)
  107. }
  108. return option
  109. }
  110. // Args returns a list of positional arguments associated with this command.
  111. func (c *Command) Args() []*Arg {
  112. ret := make([]*Arg, len(c.args))
  113. copy(ret, c.args)
  114. return ret
  115. }
  116. func newCommand(name string, shortDescription string, longDescription string, data interface{}) *Command {
  117. return &Command{
  118. Group: newGroup(shortDescription, longDescription, data),
  119. Name: name,
  120. }
  121. }
  122. func (c *Command) scanSubcommandHandler(parentg *Group) scanHandler {
  123. f := func(realval reflect.Value, sfield *reflect.StructField) (bool, error) {
  124. mtag := newMultiTag(string(sfield.Tag))
  125. if err := mtag.Parse(); err != nil {
  126. return true, err
  127. }
  128. positional := mtag.Get("positional-args")
  129. if len(positional) != 0 {
  130. stype := realval.Type()
  131. for i := 0; i < stype.NumField(); i++ {
  132. field := stype.Field(i)
  133. m := newMultiTag((string(field.Tag)))
  134. if err := m.Parse(); err != nil {
  135. return true, err
  136. }
  137. name := m.Get("positional-arg-name")
  138. if len(name) == 0 {
  139. name = field.Name
  140. }
  141. required := -1
  142. requiredMaximum := -1
  143. sreq := m.Get("required")
  144. if sreq != "" {
  145. required = 1
  146. rng := strings.SplitN(sreq, "-", 2)
  147. if len(rng) > 1 {
  148. if preq, err := strconv.ParseInt(rng[0], 10, 32); err == nil {
  149. required = int(preq)
  150. }
  151. if preq, err := strconv.ParseInt(rng[1], 10, 32); err == nil {
  152. requiredMaximum = int(preq)
  153. }
  154. } else {
  155. if preq, err := strconv.ParseInt(sreq, 10, 32); err == nil {
  156. required = int(preq)
  157. }
  158. }
  159. }
  160. arg := &Arg{
  161. Name: name,
  162. Description: m.Get("description"),
  163. Required: required,
  164. RequiredMaximum: requiredMaximum,
  165. value: realval.Field(i),
  166. tag: m,
  167. }
  168. c.args = append(c.args, arg)
  169. if len(mtag.Get("required")) != 0 {
  170. c.ArgsRequired = true
  171. }
  172. }
  173. return true, nil
  174. }
  175. subcommand := mtag.Get("command")
  176. if len(subcommand) != 0 {
  177. var ptrval reflect.Value
  178. if realval.Kind() == reflect.Ptr {
  179. ptrval = realval
  180. if ptrval.IsNil() {
  181. ptrval.Set(reflect.New(ptrval.Type().Elem()))
  182. }
  183. } else {
  184. ptrval = realval.Addr()
  185. }
  186. shortDescription := mtag.Get("description")
  187. longDescription := mtag.Get("long-description")
  188. subcommandsOptional := mtag.Get("subcommands-optional")
  189. aliases := mtag.GetMany("alias")
  190. subc, err := c.AddCommand(subcommand, shortDescription, longDescription, ptrval.Interface())
  191. if err != nil {
  192. return true, err
  193. }
  194. subc.Hidden = mtag.Get("hidden") != ""
  195. if len(subcommandsOptional) > 0 {
  196. subc.SubcommandsOptional = true
  197. }
  198. if len(aliases) > 0 {
  199. subc.Aliases = aliases
  200. }
  201. return true, nil
  202. }
  203. return parentg.scanSubGroupHandler(realval, sfield)
  204. }
  205. return f
  206. }
  207. func (c *Command) scan() error {
  208. return c.scanType(c.scanSubcommandHandler(c.Group))
  209. }
  210. func (c *Command) eachOption(f func(*Command, *Group, *Option)) {
  211. c.eachCommand(func(c *Command) {
  212. c.eachGroup(func(g *Group) {
  213. for _, option := range g.options {
  214. f(c, g, option)
  215. }
  216. })
  217. }, true)
  218. }
  219. func (c *Command) eachCommand(f func(*Command), recurse bool) {
  220. f(c)
  221. for _, cc := range c.commands {
  222. if recurse {
  223. cc.eachCommand(f, true)
  224. } else {
  225. f(cc)
  226. }
  227. }
  228. }
  229. func (c *Command) eachActiveGroup(f func(cc *Command, g *Group)) {
  230. c.eachGroup(func(g *Group) {
  231. f(c, g)
  232. })
  233. if c.Active != nil {
  234. c.Active.eachActiveGroup(f)
  235. }
  236. }
  237. func (c *Command) addHelpGroups(showHelp func() error) {
  238. if !c.hasBuiltinHelpGroup {
  239. c.addHelpGroup(showHelp)
  240. c.hasBuiltinHelpGroup = true
  241. }
  242. for _, cc := range c.commands {
  243. cc.addHelpGroups(showHelp)
  244. }
  245. }
  246. func (c *Command) makeLookup() lookup {
  247. ret := lookup{
  248. shortNames: make(map[string]*Option),
  249. longNames: make(map[string]*Option),
  250. commands: make(map[string]*Command),
  251. }
  252. parent := c.parent
  253. var parents []*Command
  254. for parent != nil {
  255. if cmd, ok := parent.(*Command); ok {
  256. parents = append(parents, cmd)
  257. parent = cmd.parent
  258. } else {
  259. parent = nil
  260. }
  261. }
  262. for i := len(parents) - 1; i >= 0; i-- {
  263. parents[i].fillLookup(&ret, true)
  264. }
  265. c.fillLookup(&ret, false)
  266. return ret
  267. }
  268. func (c *Command) fillLookup(ret *lookup, onlyOptions bool) {
  269. c.eachGroup(func(g *Group) {
  270. for _, option := range g.options {
  271. if option.ShortName != 0 {
  272. ret.shortNames[string(option.ShortName)] = option
  273. }
  274. if len(option.LongName) > 0 {
  275. ret.longNames[option.LongNameWithNamespace()] = option
  276. }
  277. }
  278. })
  279. if onlyOptions {
  280. return
  281. }
  282. for _, subcommand := range c.commands {
  283. ret.commands[subcommand.Name] = subcommand
  284. for _, a := range subcommand.Aliases {
  285. ret.commands[a] = subcommand
  286. }
  287. }
  288. }
  289. func (c *Command) groupByName(name string) *Group {
  290. if grp := c.Group.groupByName(name); grp != nil {
  291. return grp
  292. }
  293. for _, subc := range c.commands {
  294. prefix := subc.Name + "."
  295. if strings.HasPrefix(name, prefix) {
  296. if grp := subc.groupByName(name[len(prefix):]); grp != nil {
  297. return grp
  298. }
  299. } else if name == subc.Name {
  300. return subc.Group
  301. }
  302. }
  303. return nil
  304. }
  305. type commandList []*Command
  306. func (c commandList) Less(i, j int) bool {
  307. return c[i].Name < c[j].Name
  308. }
  309. func (c commandList) Len() int {
  310. return len(c)
  311. }
  312. func (c commandList) Swap(i, j int) {
  313. c[i], c[j] = c[j], c[i]
  314. }
  315. func (c *Command) sortedVisibleCommands() []*Command {
  316. ret := commandList(c.visibleCommands())
  317. sort.Sort(ret)
  318. return []*Command(ret)
  319. }
  320. func (c *Command) visibleCommands() []*Command {
  321. ret := make([]*Command, 0, len(c.commands))
  322. for _, cmd := range c.commands {
  323. if !cmd.Hidden {
  324. ret = append(ret, cmd)
  325. }
  326. }
  327. return ret
  328. }
  329. func (c *Command) match(name string) bool {
  330. if c.Name == name {
  331. return true
  332. }
  333. for _, v := range c.Aliases {
  334. if v == name {
  335. return true
  336. }
  337. }
  338. return false
  339. }
  340. func (c *Command) hasCliOptions() bool {
  341. ret := false
  342. c.eachGroup(func(g *Group) {
  343. if g.isBuiltinHelp {
  344. return
  345. }
  346. for _, opt := range g.options {
  347. if opt.canCli() {
  348. ret = true
  349. }
  350. }
  351. })
  352. return ret
  353. }
  354. func (c *Command) fillParseState(s *parseState) {
  355. s.positional = make([]*Arg, len(c.args))
  356. copy(s.positional, c.args)
  357. s.lookup = c.makeLookup()
  358. s.command = c
  359. }