cli.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  1. package cli
  2. import (
  3. "fmt"
  4. "io"
  5. "os"
  6. "regexp"
  7. "sort"
  8. "strings"
  9. "sync"
  10. "text/template"
  11. "github.com/armon/go-radix"
  12. "github.com/posener/complete"
  13. )
  14. // CLI contains the state necessary to run subcommands and parse the
  15. // command line arguments.
  16. //
  17. // CLI also supports nested subcommands, such as "cli foo bar". To use
  18. // nested subcommands, the key in the Commands mapping below contains the
  19. // full subcommand. In this example, it would be "foo bar".
  20. //
  21. // If you use a CLI with nested subcommands, some semantics change due to
  22. // ambiguities:
  23. //
  24. // * We use longest prefix matching to find a matching subcommand. This
  25. // means if you register "foo bar" and the user executes "cli foo qux",
  26. // the "foo" command will be executed with the arg "qux". It is up to
  27. // you to handle these args. One option is to just return the special
  28. // help return code `RunResultHelp` to display help and exit.
  29. //
  30. // * The help flag "-h" or "-help" will look at all args to determine
  31. // the help function. For example: "otto apps list -h" will show the
  32. // help for "apps list" but "otto apps -h" will show it for "apps".
  33. // In the normal CLI, only the first subcommand is used.
  34. //
  35. // * The help flag will list any subcommands that a command takes
  36. // as well as the command's help itself. If there are no subcommands,
  37. // it will note this. If the CLI itself has no subcommands, this entire
  38. // section is omitted.
  39. //
  40. // * Any parent commands that don't exist are automatically created as
  41. // no-op commands that just show help for other subcommands. For example,
  42. // if you only register "foo bar", then "foo" is automatically created.
  43. //
  44. type CLI struct {
  45. // Args is the list of command-line arguments received excluding
  46. // the name of the app. For example, if the command "./cli foo bar"
  47. // was invoked, then Args should be []string{"foo", "bar"}.
  48. Args []string
  49. // Commands is a mapping of subcommand names to a factory function
  50. // for creating that Command implementation. If there is a command
  51. // with a blank string "", then it will be used as the default command
  52. // if no subcommand is specified.
  53. //
  54. // If the key has a space in it, this will create a nested subcommand.
  55. // For example, if the key is "foo bar", then to access it our CLI
  56. // must be accessed with "./cli foo bar". See the docs for CLI for
  57. // notes on how this changes some other behavior of the CLI as well.
  58. //
  59. // The factory should be as cheap as possible, ideally only allocating
  60. // a struct. The factory may be called multiple times in the course
  61. // of a command execution and certain events such as help require the
  62. // instantiation of all commands. Expensive initialization should be
  63. // deferred to function calls within the interface implementation.
  64. Commands map[string]CommandFactory
  65. // HiddenCommands is a list of commands that are "hidden". Hidden
  66. // commands are not given to the help function callback and do not
  67. // show up in autocomplete. The values in the slice should be equivalent
  68. // to the keys in the command map.
  69. HiddenCommands []string
  70. // Name defines the name of the CLI.
  71. Name string
  72. // Version of the CLI.
  73. Version string
  74. // Autocomplete enables or disables subcommand auto-completion support.
  75. // This is enabled by default when NewCLI is called. Otherwise, this
  76. // must enabled explicitly.
  77. //
  78. // Autocomplete requires the "Name" option to be set on CLI. This name
  79. // should be set exactly to the binary name that is autocompleted.
  80. //
  81. // Autocompletion is supported via the github.com/posener/complete
  82. // library. This library supports bash, zsh and fish. To add support
  83. // for other shells, please see that library.
  84. //
  85. // AutocompleteInstall and AutocompleteUninstall are the global flag
  86. // names for installing and uninstalling the autocompletion handlers
  87. // for the user's shell. The flag should omit the hyphen(s) in front of
  88. // the value. Both single and double hyphens will automatically be supported
  89. // for the flag name. These default to `autocomplete-install` and
  90. // `autocomplete-uninstall` respectively.
  91. //
  92. // AutocompleteNoDefaultFlags is a boolean which controls if the default auto-
  93. // complete flags like -help and -version are added to the output.
  94. //
  95. // AutocompleteGlobalFlags are a mapping of global flags for
  96. // autocompletion. The help and version flags are automatically added.
  97. Autocomplete bool
  98. AutocompleteInstall string
  99. AutocompleteUninstall string
  100. AutocompleteNoDefaultFlags bool
  101. AutocompleteGlobalFlags complete.Flags
  102. autocompleteInstaller autocompleteInstaller // For tests
  103. // HelpFunc and HelpWriter are used to output help information, if
  104. // requested.
  105. //
  106. // HelpFunc is the function called to generate the generic help
  107. // text that is shown if help must be shown for the CLI that doesn't
  108. // pertain to a specific command.
  109. //
  110. // HelpWriter is the Writer where the help text is outputted to. If
  111. // not specified, it will default to Stderr.
  112. HelpFunc HelpFunc
  113. HelpWriter io.Writer
  114. //---------------------------------------------------------------
  115. // Internal fields set automatically
  116. once sync.Once
  117. autocomplete *complete.Complete
  118. commandTree *radix.Tree
  119. commandNested bool
  120. commandHidden map[string]struct{}
  121. subcommand string
  122. subcommandArgs []string
  123. topFlags []string
  124. // These are true when special global flags are set. We can/should
  125. // probably use a bitset for this one day.
  126. isHelp bool
  127. isVersion bool
  128. isAutocompleteInstall bool
  129. isAutocompleteUninstall bool
  130. }
  131. // NewClI returns a new CLI instance with sensible defaults.
  132. func NewCLI(app, version string) *CLI {
  133. return &CLI{
  134. Name: app,
  135. Version: version,
  136. HelpFunc: BasicHelpFunc(app),
  137. Autocomplete: true,
  138. }
  139. }
  140. // IsHelp returns whether or not the help flag is present within the
  141. // arguments.
  142. func (c *CLI) IsHelp() bool {
  143. c.once.Do(c.init)
  144. return c.isHelp
  145. }
  146. // IsVersion returns whether or not the version flag is present within the
  147. // arguments.
  148. func (c *CLI) IsVersion() bool {
  149. c.once.Do(c.init)
  150. return c.isVersion
  151. }
  152. // Run runs the actual CLI based on the arguments given.
  153. func (c *CLI) Run() (int, error) {
  154. c.once.Do(c.init)
  155. // If this is a autocompletion request, satisfy it. This must be called
  156. // first before anything else since its possible to be autocompleting
  157. // -help or -version or other flags and we want to show completions
  158. // and not actually write the help or version.
  159. if c.Autocomplete && c.autocomplete.Complete() {
  160. return 0, nil
  161. }
  162. // Just show the version and exit if instructed.
  163. if c.IsVersion() && c.Version != "" {
  164. c.HelpWriter.Write([]byte(c.Version + "\n"))
  165. return 0, nil
  166. }
  167. // Just print the help when only '-h' or '--help' is passed.
  168. if c.IsHelp() && c.Subcommand() == "" {
  169. c.HelpWriter.Write([]byte(c.HelpFunc(c.helpCommands(c.Subcommand())) + "\n"))
  170. return 0, nil
  171. }
  172. // If we're attempting to install or uninstall autocomplete then handle
  173. if c.Autocomplete {
  174. // Autocomplete requires the "Name" to be set so that we know what
  175. // command to setup the autocomplete on.
  176. if c.Name == "" {
  177. return 1, fmt.Errorf(
  178. "internal error: CLI.Name must be specified for autocomplete to work")
  179. }
  180. // If both install and uninstall flags are specified, then error
  181. if c.isAutocompleteInstall && c.isAutocompleteUninstall {
  182. return 1, fmt.Errorf(
  183. "Either the autocomplete install or uninstall flag may " +
  184. "be specified, but not both.")
  185. }
  186. // If the install flag is specified, perform the install or uninstall
  187. if c.isAutocompleteInstall {
  188. if err := c.autocompleteInstaller.Install(c.Name); err != nil {
  189. return 1, err
  190. }
  191. return 0, nil
  192. }
  193. if c.isAutocompleteUninstall {
  194. if err := c.autocompleteInstaller.Uninstall(c.Name); err != nil {
  195. return 1, err
  196. }
  197. return 0, nil
  198. }
  199. }
  200. // Attempt to get the factory function for creating the command
  201. // implementation. If the command is invalid or blank, it is an error.
  202. raw, ok := c.commandTree.Get(c.Subcommand())
  203. if !ok {
  204. c.HelpWriter.Write([]byte(c.HelpFunc(c.helpCommands(c.subcommandParent())) + "\n"))
  205. return 127, nil
  206. }
  207. command, err := raw.(CommandFactory)()
  208. if err != nil {
  209. return 1, err
  210. }
  211. // If we've been instructed to just print the help, then print it
  212. if c.IsHelp() {
  213. c.commandHelp(command)
  214. return 0, nil
  215. }
  216. // If there is an invalid flag, then error
  217. if len(c.topFlags) > 0 {
  218. c.HelpWriter.Write([]byte(
  219. "Invalid flags before the subcommand. If these flags are for\n" +
  220. "the subcommand, please put them after the subcommand.\n\n"))
  221. c.commandHelp(command)
  222. return 1, nil
  223. }
  224. code := command.Run(c.SubcommandArgs())
  225. if code == RunResultHelp {
  226. // Requesting help
  227. c.commandHelp(command)
  228. return 1, nil
  229. }
  230. return code, nil
  231. }
  232. // Subcommand returns the subcommand that the CLI would execute. For
  233. // example, a CLI from "--version version --help" would return a Subcommand
  234. // of "version"
  235. func (c *CLI) Subcommand() string {
  236. c.once.Do(c.init)
  237. return c.subcommand
  238. }
  239. // SubcommandArgs returns the arguments that will be passed to the
  240. // subcommand.
  241. func (c *CLI) SubcommandArgs() []string {
  242. c.once.Do(c.init)
  243. return c.subcommandArgs
  244. }
  245. // subcommandParent returns the parent of this subcommand, if there is one.
  246. // If there isn't on, "" is returned.
  247. func (c *CLI) subcommandParent() string {
  248. // Get the subcommand, if it is "" alread just return
  249. sub := c.Subcommand()
  250. if sub == "" {
  251. return sub
  252. }
  253. // Clear any trailing spaces and find the last space
  254. sub = strings.TrimRight(sub, " ")
  255. idx := strings.LastIndex(sub, " ")
  256. if idx == -1 {
  257. // No space means our parent is root
  258. return ""
  259. }
  260. return sub[:idx]
  261. }
  262. func (c *CLI) init() {
  263. if c.HelpFunc == nil {
  264. c.HelpFunc = BasicHelpFunc("app")
  265. if c.Name != "" {
  266. c.HelpFunc = BasicHelpFunc(c.Name)
  267. }
  268. }
  269. if c.HelpWriter == nil {
  270. c.HelpWriter = os.Stderr
  271. }
  272. // Build our hidden commands
  273. if len(c.HiddenCommands) > 0 {
  274. c.commandHidden = make(map[string]struct{})
  275. for _, h := range c.HiddenCommands {
  276. c.commandHidden[h] = struct{}{}
  277. }
  278. }
  279. // Build our command tree
  280. c.commandTree = radix.New()
  281. c.commandNested = false
  282. for k, v := range c.Commands {
  283. k = strings.TrimSpace(k)
  284. c.commandTree.Insert(k, v)
  285. if strings.ContainsRune(k, ' ') {
  286. c.commandNested = true
  287. }
  288. }
  289. // Go through the key and fill in any missing parent commands
  290. if c.commandNested {
  291. var walkFn radix.WalkFn
  292. toInsert := make(map[string]struct{})
  293. walkFn = func(k string, raw interface{}) bool {
  294. idx := strings.LastIndex(k, " ")
  295. if idx == -1 {
  296. // If there is no space, just ignore top level commands
  297. return false
  298. }
  299. // Trim up to that space so we can get the expected parent
  300. k = k[:idx]
  301. if _, ok := c.commandTree.Get(k); ok {
  302. // Yay we have the parent!
  303. return false
  304. }
  305. // We're missing the parent, so let's insert this
  306. toInsert[k] = struct{}{}
  307. // Call the walk function recursively so we check this one too
  308. return walkFn(k, nil)
  309. }
  310. // Walk!
  311. c.commandTree.Walk(walkFn)
  312. // Insert any that we're missing
  313. for k := range toInsert {
  314. var f CommandFactory = func() (Command, error) {
  315. return &MockCommand{
  316. HelpText: "This command is accessed by using one of the subcommands below.",
  317. RunResult: RunResultHelp,
  318. }, nil
  319. }
  320. c.commandTree.Insert(k, f)
  321. }
  322. }
  323. // Setup autocomplete if we have it enabled. We have to do this after
  324. // the command tree is setup so we can use the radix tree to easily find
  325. // all subcommands.
  326. if c.Autocomplete {
  327. c.initAutocomplete()
  328. }
  329. // Process the args
  330. c.processArgs()
  331. }
  332. func (c *CLI) initAutocomplete() {
  333. if c.AutocompleteInstall == "" {
  334. c.AutocompleteInstall = defaultAutocompleteInstall
  335. }
  336. if c.AutocompleteUninstall == "" {
  337. c.AutocompleteUninstall = defaultAutocompleteUninstall
  338. }
  339. if c.autocompleteInstaller == nil {
  340. c.autocompleteInstaller = &realAutocompleteInstaller{}
  341. }
  342. // Build the root command
  343. cmd := c.initAutocompleteSub("")
  344. // For the root, we add the global flags to the "Flags". This way
  345. // they don't show up on every command.
  346. if !c.AutocompleteNoDefaultFlags {
  347. cmd.Flags = map[string]complete.Predictor{
  348. "-" + c.AutocompleteInstall: complete.PredictNothing,
  349. "-" + c.AutocompleteUninstall: complete.PredictNothing,
  350. "-help": complete.PredictNothing,
  351. "-version": complete.PredictNothing,
  352. }
  353. }
  354. cmd.GlobalFlags = c.AutocompleteGlobalFlags
  355. c.autocomplete = complete.New(c.Name, cmd)
  356. }
  357. // initAutocompleteSub creates the complete.Command for a subcommand with
  358. // the given prefix. This will continue recursively for all subcommands.
  359. // The prefix "" (empty string) can be used for the root command.
  360. func (c *CLI) initAutocompleteSub(prefix string) complete.Command {
  361. var cmd complete.Command
  362. walkFn := func(k string, raw interface{}) bool {
  363. // Ignore the empty key which can be present for default commands.
  364. if k == "" {
  365. return false
  366. }
  367. // Keep track of the full key so that we can nest further if necessary
  368. fullKey := k
  369. if len(prefix) > 0 {
  370. // If we have a prefix, trim the prefix + 1 (for the space)
  371. // Example: turns "sub one" to "one" with prefix "sub"
  372. k = k[len(prefix)+1:]
  373. }
  374. if idx := strings.Index(k, " "); idx >= 0 {
  375. // If there is a space, we trim up to the space. This turns
  376. // "sub sub2 sub3" into "sub". The prefix trim above will
  377. // trim our current depth properly.
  378. k = k[:idx]
  379. }
  380. if _, ok := cmd.Sub[k]; ok {
  381. // If we already tracked this subcommand then ignore
  382. return false
  383. }
  384. // If the command is hidden, don't record it at all
  385. if _, ok := c.commandHidden[fullKey]; ok {
  386. return false
  387. }
  388. if cmd.Sub == nil {
  389. cmd.Sub = complete.Commands(make(map[string]complete.Command))
  390. }
  391. subCmd := c.initAutocompleteSub(fullKey)
  392. // Instantiate the command so that we can check if the command is
  393. // a CommandAutocomplete implementation. If there is an error
  394. // creating the command, we just ignore it since that will be caught
  395. // later.
  396. impl, err := raw.(CommandFactory)()
  397. if err != nil {
  398. impl = nil
  399. }
  400. // Check if it implements ComandAutocomplete. If so, setup the autocomplete
  401. if c, ok := impl.(CommandAutocomplete); ok {
  402. subCmd.Args = c.AutocompleteArgs()
  403. subCmd.Flags = c.AutocompleteFlags()
  404. }
  405. cmd.Sub[k] = subCmd
  406. return false
  407. }
  408. walkPrefix := prefix
  409. if walkPrefix != "" {
  410. walkPrefix += " "
  411. }
  412. c.commandTree.WalkPrefix(walkPrefix, walkFn)
  413. return cmd
  414. }
  415. func (c *CLI) commandHelp(command Command) {
  416. // Get the template to use
  417. tpl := strings.TrimSpace(defaultHelpTemplate)
  418. if t, ok := command.(CommandHelpTemplate); ok {
  419. tpl = t.HelpTemplate()
  420. }
  421. if !strings.HasSuffix(tpl, "\n") {
  422. tpl += "\n"
  423. }
  424. // Parse it
  425. t, err := template.New("root").Parse(tpl)
  426. if err != nil {
  427. t = template.Must(template.New("root").Parse(fmt.Sprintf(
  428. "Internal error! Failed to parse command help template: %s\n", err)))
  429. }
  430. // Template data
  431. data := map[string]interface{}{
  432. "Name": c.Name,
  433. "Help": command.Help(),
  434. }
  435. // Build subcommand list if we have it
  436. var subcommandsTpl []map[string]interface{}
  437. if c.commandNested {
  438. // Get the matching keys
  439. subcommands := c.helpCommands(c.Subcommand())
  440. keys := make([]string, 0, len(subcommands))
  441. for k := range subcommands {
  442. keys = append(keys, k)
  443. }
  444. // Sort the keys
  445. sort.Strings(keys)
  446. // Figure out the padding length
  447. var longest int
  448. for _, k := range keys {
  449. if v := len(k); v > longest {
  450. longest = v
  451. }
  452. }
  453. // Go through and create their structures
  454. subcommandsTpl = make([]map[string]interface{}, 0, len(subcommands))
  455. for _, k := range keys {
  456. // Get the command
  457. raw, ok := subcommands[k]
  458. if !ok {
  459. c.HelpWriter.Write([]byte(fmt.Sprintf(
  460. "Error getting subcommand %q", k)))
  461. }
  462. sub, err := raw()
  463. if err != nil {
  464. c.HelpWriter.Write([]byte(fmt.Sprintf(
  465. "Error instantiating %q: %s", k, err)))
  466. }
  467. // Find the last space and make sure we only include that last part
  468. name := k
  469. if idx := strings.LastIndex(k, " "); idx > -1 {
  470. name = name[idx+1:]
  471. }
  472. subcommandsTpl = append(subcommandsTpl, map[string]interface{}{
  473. "Name": name,
  474. "NameAligned": name + strings.Repeat(" ", longest-len(k)),
  475. "Help": sub.Help(),
  476. "Synopsis": sub.Synopsis(),
  477. })
  478. }
  479. }
  480. data["Subcommands"] = subcommandsTpl
  481. // Write
  482. err = t.Execute(c.HelpWriter, data)
  483. if err == nil {
  484. return
  485. }
  486. // An error, just output...
  487. c.HelpWriter.Write([]byte(fmt.Sprintf(
  488. "Internal error rendering help: %s", err)))
  489. }
  490. // helpCommands returns the subcommands for the HelpFunc argument.
  491. // This will only contain immediate subcommands.
  492. func (c *CLI) helpCommands(prefix string) map[string]CommandFactory {
  493. // If our prefix isn't empty, make sure it ends in ' '
  494. if prefix != "" && prefix[len(prefix)-1] != ' ' {
  495. prefix += " "
  496. }
  497. // Get all the subkeys of this command
  498. var keys []string
  499. c.commandTree.WalkPrefix(prefix, func(k string, raw interface{}) bool {
  500. // Ignore any sub-sub keys, i.e. "foo bar baz" when we want "foo bar"
  501. if !strings.Contains(k[len(prefix):], " ") {
  502. keys = append(keys, k)
  503. }
  504. return false
  505. })
  506. // For each of the keys return that in the map
  507. result := make(map[string]CommandFactory, len(keys))
  508. for _, k := range keys {
  509. raw, ok := c.commandTree.Get(k)
  510. if !ok {
  511. // We just got it via WalkPrefix above, so we just panic
  512. panic("not found: " + k)
  513. }
  514. // If this is a hidden command, don't show it
  515. if _, ok := c.commandHidden[k]; ok {
  516. continue
  517. }
  518. result[k] = raw.(CommandFactory)
  519. }
  520. return result
  521. }
  522. func (c *CLI) processArgs() {
  523. for i, arg := range c.Args {
  524. if arg == "--" {
  525. break
  526. }
  527. // Check for help flags.
  528. if arg == "-h" || arg == "-help" || arg == "--help" {
  529. c.isHelp = true
  530. continue
  531. }
  532. // Check for autocomplete flags
  533. if c.Autocomplete {
  534. if arg == "-"+c.AutocompleteInstall || arg == "--"+c.AutocompleteInstall {
  535. c.isAutocompleteInstall = true
  536. continue
  537. }
  538. if arg == "-"+c.AutocompleteUninstall || arg == "--"+c.AutocompleteUninstall {
  539. c.isAutocompleteUninstall = true
  540. continue
  541. }
  542. }
  543. if c.subcommand == "" {
  544. // Check for version flags if not in a subcommand.
  545. if arg == "-v" || arg == "-version" || arg == "--version" {
  546. c.isVersion = true
  547. continue
  548. }
  549. if arg != "" && arg[0] == '-' {
  550. // Record the arg...
  551. c.topFlags = append(c.topFlags, arg)
  552. }
  553. }
  554. // If we didn't find a subcommand yet and this is the first non-flag
  555. // argument, then this is our subcommand.
  556. if c.subcommand == "" && arg != "" && arg[0] != '-' {
  557. c.subcommand = arg
  558. if c.commandNested {
  559. // If the command has a space in it, then it is invalid.
  560. // Set a blank command so that it fails.
  561. if strings.ContainsRune(arg, ' ') {
  562. c.subcommand = ""
  563. return
  564. }
  565. // Determine the argument we look to to end subcommands.
  566. // We look at all arguments until one has a space. This
  567. // disallows commands like: ./cli foo "bar baz". An argument
  568. // with a space is always an argument.
  569. j := 0
  570. for k, v := range c.Args[i:] {
  571. if strings.ContainsRune(v, ' ') {
  572. break
  573. }
  574. j = i + k + 1
  575. }
  576. // Nested CLI, the subcommand is actually the entire
  577. // arg list up to a flag that is still a valid subcommand.
  578. searchKey := strings.Join(c.Args[i:j], " ")
  579. k, _, ok := c.commandTree.LongestPrefix(searchKey)
  580. if ok {
  581. // k could be a prefix that doesn't contain the full
  582. // command such as "foo" instead of "foobar", so we
  583. // need to verify that we have an entire key. To do that,
  584. // we look for an ending in a space or an end of string.
  585. reVerify := regexp.MustCompile(regexp.QuoteMeta(k) + `( |$)`)
  586. if reVerify.MatchString(searchKey) {
  587. c.subcommand = k
  588. i += strings.Count(k, " ")
  589. }
  590. }
  591. }
  592. // The remaining args the subcommand arguments
  593. c.subcommandArgs = c.Args[i+1:]
  594. }
  595. }
  596. // If we never found a subcommand and support a default command, then
  597. // switch to using that.
  598. if c.subcommand == "" {
  599. if _, ok := c.Commands[""]; ok {
  600. args := c.topFlags
  601. args = append(args, c.subcommandArgs...)
  602. c.topFlags = nil
  603. c.subcommandArgs = args
  604. }
  605. }
  606. }
  607. // defaultAutocompleteInstall and defaultAutocompleteUninstall are the
  608. // default values for the autocomplete install and uninstall flags.
  609. const defaultAutocompleteInstall = "autocomplete-install"
  610. const defaultAutocompleteUninstall = "autocomplete-uninstall"
  611. const defaultHelpTemplate = `
  612. {{.Help}}{{if gt (len .Subcommands) 0}}
  613. Subcommands:
  614. {{- range $value := .Subcommands }}
  615. {{ $value.NameAligned }} {{ $value.Synopsis }}{{ end }}
  616. {{- end }}
  617. `