completions.go 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020
  1. // Copyright 2013-2023 The Cobra Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package cobra
  15. import (
  16. "fmt"
  17. "os"
  18. "regexp"
  19. "strconv"
  20. "strings"
  21. "sync"
  22. "github.com/spf13/pflag"
  23. )
  24. const (
  25. // ShellCompRequestCmd is the name of the hidden command that is used to request
  26. // completion results from the program. It is used by the shell completion scripts.
  27. ShellCompRequestCmd = "__complete"
  28. // ShellCompNoDescRequestCmd is the name of the hidden command that is used to request
  29. // completion results without their description. It is used by the shell completion scripts.
  30. ShellCompNoDescRequestCmd = "__completeNoDesc"
  31. )
  32. // Global map of flag completion functions. Make sure to use flagCompletionMutex before you try to read and write from it.
  33. var flagCompletionFunctions = map[*pflag.Flag]CompletionFunc{}
  34. // lock for reading and writing from flagCompletionFunctions
  35. var flagCompletionMutex = &sync.RWMutex{}
  36. // ShellCompDirective is a bit map representing the different behaviors the shell
  37. // can be instructed to have once completions have been provided.
  38. type ShellCompDirective int
  39. type flagCompError struct {
  40. subCommand string
  41. flagName string
  42. }
  43. func (e *flagCompError) Error() string {
  44. return "Subcommand '" + e.subCommand + "' does not support flag '" + e.flagName + "'"
  45. }
  46. const (
  47. // ShellCompDirectiveError indicates an error occurred and completions should be ignored.
  48. ShellCompDirectiveError ShellCompDirective = 1 << iota
  49. // ShellCompDirectiveNoSpace indicates that the shell should not add a space
  50. // after the completion even if there is a single completion provided.
  51. ShellCompDirectiveNoSpace
  52. // ShellCompDirectiveNoFileComp indicates that the shell should not provide
  53. // file completion even when no completion is provided.
  54. ShellCompDirectiveNoFileComp
  55. // ShellCompDirectiveFilterFileExt indicates that the provided completions
  56. // should be used as file extension filters.
  57. // For flags, using Command.MarkFlagFilename() and Command.MarkPersistentFlagFilename()
  58. // is a shortcut to using this directive explicitly. The BashCompFilenameExt
  59. // annotation can also be used to obtain the same behavior for flags.
  60. ShellCompDirectiveFilterFileExt
  61. // ShellCompDirectiveFilterDirs indicates that only directory names should
  62. // be provided in file completion. To request directory names within another
  63. // directory, the returned completions should specify the directory within
  64. // which to search. The BashCompSubdirsInDir annotation can be used to
  65. // obtain the same behavior but only for flags.
  66. ShellCompDirectiveFilterDirs
  67. // ShellCompDirectiveKeepOrder indicates that the shell should preserve the order
  68. // in which the completions are provided
  69. ShellCompDirectiveKeepOrder
  70. // ===========================================================================
  71. // All directives using iota should be above this one.
  72. // For internal use.
  73. shellCompDirectiveMaxValue
  74. // ShellCompDirectiveDefault indicates to let the shell perform its default
  75. // behavior after completions have been provided.
  76. // This one must be last to avoid messing up the iota count.
  77. ShellCompDirectiveDefault ShellCompDirective = 0
  78. )
  79. const (
  80. // Constants for the completion command
  81. compCmdName = "completion"
  82. compCmdNoDescFlagName = "no-descriptions"
  83. compCmdNoDescFlagDesc = "disable completion descriptions"
  84. compCmdNoDescFlagDefault = false
  85. )
  86. // CompletionOptions are the options to control shell completion
  87. type CompletionOptions struct {
  88. // DisableDefaultCmd prevents Cobra from creating a default 'completion' command
  89. DisableDefaultCmd bool
  90. // DisableNoDescFlag prevents Cobra from creating the '--no-descriptions' flag
  91. // for shells that support completion descriptions
  92. DisableNoDescFlag bool
  93. // DisableDescriptions turns off all completion descriptions for shells
  94. // that support them
  95. DisableDescriptions bool
  96. // HiddenDefaultCmd makes the default 'completion' command hidden
  97. HiddenDefaultCmd bool
  98. // DefaultShellCompDirective sets the ShellCompDirective that is returned
  99. // if no special directive can be determined
  100. DefaultShellCompDirective *ShellCompDirective
  101. }
  102. func (receiver *CompletionOptions) SetDefaultShellCompDirective(directive ShellCompDirective) {
  103. receiver.DefaultShellCompDirective = &directive
  104. }
  105. // Completion is a string that can be used for completions
  106. //
  107. // two formats are supported:
  108. // - the completion choice
  109. // - the completion choice with a textual description (separated by a TAB).
  110. //
  111. // [CompletionWithDesc] can be used to create a completion string with a textual description.
  112. //
  113. // Note: Go type alias is used to provide a more descriptive name in the documentation, but any string can be used.
  114. type Completion = string
  115. // CompletionFunc is a function that provides completion results.
  116. type CompletionFunc = func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective)
  117. // CompletionWithDesc returns a [Completion] with a description by using the TAB delimited format.
  118. func CompletionWithDesc(choice string, description string) Completion {
  119. return choice + "\t" + description
  120. }
  121. // NoFileCompletions can be used to disable file completion for commands that should
  122. // not trigger file completions.
  123. //
  124. // This method satisfies [CompletionFunc].
  125. // It can be used with [Command.RegisterFlagCompletionFunc] and for [Command.ValidArgsFunction].
  126. func NoFileCompletions(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective) {
  127. return nil, ShellCompDirectiveNoFileComp
  128. }
  129. // FixedCompletions can be used to create a completion function which always
  130. // returns the same results.
  131. //
  132. // This method returns a function that satisfies [CompletionFunc]
  133. // It can be used with [Command.RegisterFlagCompletionFunc] and for [Command.ValidArgsFunction].
  134. func FixedCompletions(choices []Completion, directive ShellCompDirective) CompletionFunc {
  135. return func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective) {
  136. return choices, directive
  137. }
  138. }
  139. // RegisterFlagCompletionFunc should be called to register a function to provide completion for a flag.
  140. //
  141. // You can use pre-defined completion functions such as [FixedCompletions] or [NoFileCompletions],
  142. // or you can define your own.
  143. func (c *Command) RegisterFlagCompletionFunc(flagName string, f CompletionFunc) error {
  144. flag := c.Flag(flagName)
  145. if flag == nil {
  146. return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' does not exist", flagName)
  147. }
  148. flagCompletionMutex.Lock()
  149. defer flagCompletionMutex.Unlock()
  150. if _, exists := flagCompletionFunctions[flag]; exists {
  151. return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' already registered", flagName)
  152. }
  153. flagCompletionFunctions[flag] = f
  154. return nil
  155. }
  156. // GetFlagCompletionFunc returns the completion function for the given flag of the command, if available.
  157. func (c *Command) GetFlagCompletionFunc(flagName string) (CompletionFunc, bool) {
  158. flag := c.Flag(flagName)
  159. if flag == nil {
  160. return nil, false
  161. }
  162. flagCompletionMutex.RLock()
  163. defer flagCompletionMutex.RUnlock()
  164. completionFunc, exists := flagCompletionFunctions[flag]
  165. return completionFunc, exists
  166. }
  167. // Returns a string listing the different directive enabled in the specified parameter
  168. func (d ShellCompDirective) string() string {
  169. var directives []string
  170. if d&ShellCompDirectiveError != 0 {
  171. directives = append(directives, "ShellCompDirectiveError")
  172. }
  173. if d&ShellCompDirectiveNoSpace != 0 {
  174. directives = append(directives, "ShellCompDirectiveNoSpace")
  175. }
  176. if d&ShellCompDirectiveNoFileComp != 0 {
  177. directives = append(directives, "ShellCompDirectiveNoFileComp")
  178. }
  179. if d&ShellCompDirectiveFilterFileExt != 0 {
  180. directives = append(directives, "ShellCompDirectiveFilterFileExt")
  181. }
  182. if d&ShellCompDirectiveFilterDirs != 0 {
  183. directives = append(directives, "ShellCompDirectiveFilterDirs")
  184. }
  185. if d&ShellCompDirectiveKeepOrder != 0 {
  186. directives = append(directives, "ShellCompDirectiveKeepOrder")
  187. }
  188. if len(directives) == 0 {
  189. directives = append(directives, "ShellCompDirectiveDefault")
  190. }
  191. if d >= shellCompDirectiveMaxValue {
  192. return fmt.Sprintf("ERROR: unexpected ShellCompDirective value: %d", d)
  193. }
  194. return strings.Join(directives, ", ")
  195. }
  196. // initCompleteCmd adds a special hidden command that can be used to request custom completions.
  197. func (c *Command) initCompleteCmd(args []string) {
  198. completeCmd := &Command{
  199. Use: fmt.Sprintf("%s [command-line]", ShellCompRequestCmd),
  200. Aliases: []string{ShellCompNoDescRequestCmd},
  201. DisableFlagsInUseLine: true,
  202. Hidden: true,
  203. DisableFlagParsing: true,
  204. Args: MinimumNArgs(1),
  205. Short: "Request shell completion choices for the specified command-line",
  206. Long: fmt.Sprintf("%[2]s is a special command that is used by the shell completion logic\n%[1]s",
  207. "to request completion choices for the specified command-line.", ShellCompRequestCmd),
  208. Run: func(cmd *Command, args []string) {
  209. finalCmd, completions, directive, err := cmd.getCompletions(args)
  210. if err != nil {
  211. CompErrorln(err.Error())
  212. // Keep going for multiple reasons:
  213. // 1- There could be some valid completions even though there was an error
  214. // 2- Even without completions, we need to print the directive
  215. }
  216. noDescriptions := cmd.CalledAs() == ShellCompNoDescRequestCmd
  217. if !noDescriptions {
  218. if doDescriptions, err := strconv.ParseBool(getEnvConfig(cmd, configEnvVarSuffixDescriptions)); err == nil {
  219. noDescriptions = !doDescriptions
  220. }
  221. }
  222. noActiveHelp := GetActiveHelpConfig(finalCmd) == activeHelpGlobalDisable
  223. out := finalCmd.OutOrStdout()
  224. for _, comp := range completions {
  225. if noActiveHelp && strings.HasPrefix(comp, activeHelpMarker) {
  226. // Remove all activeHelp entries if it's disabled.
  227. continue
  228. }
  229. if noDescriptions {
  230. // Remove any description that may be included following a tab character.
  231. comp = strings.SplitN(comp, "\t", 2)[0]
  232. }
  233. // Make sure we only write the first line to the output.
  234. // This is needed if a description contains a linebreak.
  235. // Otherwise the shell scripts will interpret the other lines as new flags
  236. // and could therefore provide a wrong completion.
  237. comp = strings.SplitN(comp, "\n", 2)[0]
  238. // Finally trim the completion. This is especially important to get rid
  239. // of a trailing tab when there are no description following it.
  240. // For example, a sub-command without a description should not be completed
  241. // with a tab at the end (or else zsh will show a -- following it
  242. // although there is no description).
  243. comp = strings.TrimSpace(comp)
  244. // Print each possible completion to the output for the completion script to consume.
  245. fmt.Fprintln(out, comp)
  246. }
  247. // As the last printout, print the completion directive for the completion script to parse.
  248. // The directive integer must be that last character following a single colon (:).
  249. // The completion script expects :<directive>
  250. fmt.Fprintf(out, ":%d\n", directive)
  251. // Print some helpful info to stderr for the user to understand.
  252. // Output from stderr must be ignored by the completion script.
  253. fmt.Fprintf(finalCmd.ErrOrStderr(), "Completion ended with directive: %s\n", directive.string())
  254. },
  255. }
  256. c.AddCommand(completeCmd)
  257. subCmd, _, err := c.Find(args)
  258. if err != nil || subCmd.Name() != ShellCompRequestCmd {
  259. // Only create this special command if it is actually being called.
  260. // This reduces possible side-effects of creating such a command;
  261. // for example, having this command would cause problems to a
  262. // cobra program that only consists of the root command, since this
  263. // command would cause the root command to suddenly have a subcommand.
  264. c.RemoveCommand(completeCmd)
  265. }
  266. }
  267. // SliceValue is a reduced version of [pflag.SliceValue]. It is used to detect
  268. // flags that accept multiple values and therefore can provide completion
  269. // multiple times.
  270. type SliceValue interface {
  271. // GetSlice returns the flag value list as an array of strings.
  272. GetSlice() []string
  273. }
  274. func (c *Command) getCompletions(args []string) (*Command, []Completion, ShellCompDirective, error) {
  275. // The last argument, which is not completely typed by the user,
  276. // should not be part of the list of arguments
  277. toComplete := args[len(args)-1]
  278. trimmedArgs := args[:len(args)-1]
  279. var finalCmd *Command
  280. var finalArgs []string
  281. var err error
  282. // Find the real command for which completion must be performed
  283. // check if we need to traverse here to parse local flags on parent commands
  284. if c.Root().TraverseChildren {
  285. finalCmd, finalArgs, err = c.Root().Traverse(trimmedArgs)
  286. } else {
  287. // For Root commands that don't specify any value for their Args fields, when we call
  288. // Find(), if those Root commands don't have any sub-commands, they will accept arguments.
  289. // However, because we have added the __complete sub-command in the current code path, the
  290. // call to Find() -> legacyArgs() will return an error if there are any arguments.
  291. // To avoid this, we first remove the __complete command to get back to having no sub-commands.
  292. rootCmd := c.Root()
  293. if len(rootCmd.Commands()) == 1 {
  294. rootCmd.RemoveCommand(c)
  295. }
  296. finalCmd, finalArgs, err = rootCmd.Find(trimmedArgs)
  297. }
  298. if err != nil {
  299. // Unable to find the real command. E.g., <program> someInvalidCmd <TAB>
  300. return c, []Completion{}, ShellCompDirectiveDefault, fmt.Errorf("unable to find a command for arguments: %v", trimmedArgs)
  301. }
  302. finalCmd.ctx = c.ctx
  303. // These flags are normally added when `execute()` is called on `finalCmd`,
  304. // however, when doing completion, we don't call `finalCmd.execute()`.
  305. // Let's add the --help and --version flag ourselves but only if the finalCmd
  306. // has not disabled flag parsing; if flag parsing is disabled, it is up to the
  307. // finalCmd itself to handle the completion of *all* flags.
  308. if !finalCmd.DisableFlagParsing {
  309. finalCmd.InitDefaultHelpFlag()
  310. finalCmd.InitDefaultVersionFlag()
  311. }
  312. // Check if we are doing flag value completion before parsing the flags.
  313. // This is important because if we are completing a flag value, we need to also
  314. // remove the flag name argument from the list of finalArgs or else the parsing
  315. // could fail due to an invalid value (incomplete) for the flag.
  316. flag, finalArgs, toComplete, flagErr := checkIfFlagCompletion(finalCmd, finalArgs, toComplete)
  317. // Check if interspersed is false or -- was set on a previous arg.
  318. // This works by counting the arguments. Normally -- is not counted as arg but
  319. // if -- was already set or interspersed is false and there is already one arg then
  320. // the extra added -- is counted as arg.
  321. flagCompletion := true
  322. _ = finalCmd.ParseFlags(append(finalArgs, "--"))
  323. newArgCount := finalCmd.Flags().NArg()
  324. // Parse the flags early so we can check if required flags are set
  325. if err = finalCmd.ParseFlags(finalArgs); err != nil {
  326. return finalCmd, []Completion{}, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error())
  327. }
  328. realArgCount := finalCmd.Flags().NArg()
  329. if newArgCount > realArgCount {
  330. // don't do flag completion (see above)
  331. flagCompletion = false
  332. }
  333. // Error while attempting to parse flags
  334. if flagErr != nil {
  335. // If error type is flagCompError and we don't want flagCompletion we should ignore the error
  336. if _, ok := flagErr.(*flagCompError); !ok || flagCompletion {
  337. return finalCmd, []Completion{}, ShellCompDirectiveDefault, flagErr
  338. }
  339. }
  340. // Look for the --help or --version flags. If they are present,
  341. // there should be no further completions.
  342. if helpOrVersionFlagPresent(finalCmd) {
  343. return finalCmd, []Completion{}, ShellCompDirectiveNoFileComp, nil
  344. }
  345. // We only remove the flags from the arguments if DisableFlagParsing is not set.
  346. // This is important for commands which have requested to do their own flag completion.
  347. if !finalCmd.DisableFlagParsing {
  348. finalArgs = finalCmd.Flags().Args()
  349. }
  350. if flag != nil && flagCompletion {
  351. // Check if we are completing a flag value subject to annotations
  352. if validExts, present := flag.Annotations[BashCompFilenameExt]; present {
  353. if len(validExts) != 0 {
  354. // File completion filtered by extensions
  355. return finalCmd, validExts, ShellCompDirectiveFilterFileExt, nil
  356. }
  357. // The annotation requests simple file completion. There is no reason to do
  358. // that since it is the default behavior anyway. Let's ignore this annotation
  359. // in case the program also registered a completion function for this flag.
  360. // Even though it is a mistake on the program's side, let's be nice when we can.
  361. }
  362. if subDir, present := flag.Annotations[BashCompSubdirsInDir]; present {
  363. if len(subDir) == 1 {
  364. // Directory completion from within a directory
  365. return finalCmd, subDir, ShellCompDirectiveFilterDirs, nil
  366. }
  367. // Directory completion
  368. return finalCmd, []Completion{}, ShellCompDirectiveFilterDirs, nil
  369. }
  370. }
  371. var completions []Completion
  372. var directive ShellCompDirective
  373. // Enforce flag groups before doing flag completions
  374. finalCmd.enforceFlagGroupsForCompletion()
  375. // Note that we want to perform flagname completion even if finalCmd.DisableFlagParsing==true;
  376. // doing this allows for completion of persistent flag names even for commands that disable flag parsing.
  377. //
  378. // When doing completion of a flag name, as soon as an argument starts with
  379. // a '-' we know it is a flag. We cannot use isFlagArg() here as it requires
  380. // the flag name to be complete
  381. if flag == nil && len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") && flagCompletion {
  382. // First check for required flags
  383. completions = completeRequireFlags(finalCmd, toComplete)
  384. // If we have not found any required flags, only then can we show regular flags
  385. if len(completions) == 0 {
  386. doCompleteFlags := func(flag *pflag.Flag) {
  387. _, acceptsMultiple := flag.Value.(SliceValue)
  388. acceptsMultiple = acceptsMultiple ||
  389. strings.Contains(flag.Value.Type(), "Slice") ||
  390. strings.Contains(flag.Value.Type(), "Array") ||
  391. strings.HasPrefix(flag.Value.Type(), "stringTo")
  392. if !flag.Changed || acceptsMultiple {
  393. // If the flag is not already present, or if it can be specified multiple times (Array, Slice, or stringTo)
  394. // we suggest it as a completion
  395. completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
  396. }
  397. }
  398. // We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands
  399. // that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and
  400. // non-inherited flags.
  401. finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
  402. doCompleteFlags(flag)
  403. })
  404. // Try to complete non-inherited flags even if DisableFlagParsing==true.
  405. // This allows programs to tell Cobra about flags for completion even
  406. // if the actual parsing of flags is not done by Cobra.
  407. // For instance, Helm uses this to provide flag name completion for
  408. // some of its plugins.
  409. finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
  410. doCompleteFlags(flag)
  411. })
  412. }
  413. directive = ShellCompDirectiveNoFileComp
  414. if len(completions) == 1 && strings.HasSuffix(completions[0], "=") {
  415. // If there is a single completion, the shell usually adds a space
  416. // after the completion. We don't want that if the flag ends with an =
  417. directive = ShellCompDirectiveNoSpace
  418. }
  419. if !finalCmd.DisableFlagParsing {
  420. // If DisableFlagParsing==false, we have completed the flags as known by Cobra;
  421. // we can return what we found.
  422. // If DisableFlagParsing==true, Cobra may not be aware of all flags, so we
  423. // let the logic continue to see if ValidArgsFunction needs to be called.
  424. return finalCmd, completions, directive, nil
  425. }
  426. } else {
  427. directive = ShellCompDirectiveDefault
  428. // check current and parent commands for a custom DefaultShellCompDirective
  429. for cmd := finalCmd; cmd != nil; cmd = cmd.parent {
  430. if cmd.CompletionOptions.DefaultShellCompDirective != nil {
  431. directive = *cmd.CompletionOptions.DefaultShellCompDirective
  432. break
  433. }
  434. }
  435. if flag == nil {
  436. foundLocalNonPersistentFlag := false
  437. // If TraverseChildren is true on the root command we don't check for
  438. // local flags because we can use a local flag on a parent command
  439. if !finalCmd.Root().TraverseChildren {
  440. // Check if there are any local, non-persistent flags on the command-line
  441. localNonPersistentFlags := finalCmd.LocalNonPersistentFlags()
  442. finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
  443. if localNonPersistentFlags.Lookup(flag.Name) != nil && flag.Changed {
  444. foundLocalNonPersistentFlag = true
  445. }
  446. })
  447. }
  448. // Complete subcommand names, including the help command
  449. if len(finalArgs) == 0 && !foundLocalNonPersistentFlag {
  450. // We only complete sub-commands if:
  451. // - there are no arguments on the command-line and
  452. // - there are no local, non-persistent flags on the command-line or TraverseChildren is true
  453. for _, subCmd := range finalCmd.Commands() {
  454. if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand {
  455. if strings.HasPrefix(subCmd.Name(), toComplete) {
  456. completions = append(completions, CompletionWithDesc(subCmd.Name(), subCmd.Short))
  457. }
  458. directive = ShellCompDirectiveNoFileComp
  459. }
  460. }
  461. }
  462. // Complete required flags even without the '-' prefix
  463. completions = append(completions, completeRequireFlags(finalCmd, toComplete)...)
  464. // Always complete ValidArgs, even if we are completing a subcommand name.
  465. // This is for commands that have both subcommands and ValidArgs.
  466. if len(finalCmd.ValidArgs) > 0 {
  467. if len(finalArgs) == 0 {
  468. // ValidArgs are only for the first argument
  469. for _, validArg := range finalCmd.ValidArgs {
  470. if strings.HasPrefix(validArg, toComplete) {
  471. completions = append(completions, validArg)
  472. }
  473. }
  474. directive = ShellCompDirectiveNoFileComp
  475. // If no completions were found within commands or ValidArgs,
  476. // see if there are any ArgAliases that should be completed.
  477. if len(completions) == 0 {
  478. for _, argAlias := range finalCmd.ArgAliases {
  479. if strings.HasPrefix(argAlias, toComplete) {
  480. completions = append(completions, argAlias)
  481. }
  482. }
  483. }
  484. }
  485. // If there are ValidArgs specified (even if they don't match), we stop completion.
  486. // Only one of ValidArgs or ValidArgsFunction can be used for a single command.
  487. return finalCmd, completions, directive, nil
  488. }
  489. // Let the logic continue so as to add any ValidArgsFunction completions,
  490. // even if we already found sub-commands.
  491. // This is for commands that have subcommands but also specify a ValidArgsFunction.
  492. }
  493. }
  494. // Find the completion function for the flag or command
  495. var completionFn CompletionFunc
  496. if flag != nil && flagCompletion {
  497. flagCompletionMutex.RLock()
  498. completionFn = flagCompletionFunctions[flag]
  499. flagCompletionMutex.RUnlock()
  500. } else {
  501. completionFn = finalCmd.ValidArgsFunction
  502. }
  503. if completionFn != nil {
  504. // Go custom completion defined for this flag or command.
  505. // Call the registered completion function to get the completions.
  506. var comps []Completion
  507. comps, directive = completionFn(finalCmd, finalArgs, toComplete)
  508. completions = append(completions, comps...)
  509. }
  510. return finalCmd, completions, directive, nil
  511. }
  512. func helpOrVersionFlagPresent(cmd *Command) bool {
  513. if versionFlag := cmd.Flags().Lookup("version"); versionFlag != nil &&
  514. len(versionFlag.Annotations[FlagSetByCobraAnnotation]) > 0 && versionFlag.Changed {
  515. return true
  516. }
  517. if helpFlag := cmd.Flags().Lookup(helpFlagName); helpFlag != nil &&
  518. len(helpFlag.Annotations[FlagSetByCobraAnnotation]) > 0 && helpFlag.Changed {
  519. return true
  520. }
  521. return false
  522. }
  523. func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []Completion {
  524. if nonCompletableFlag(flag) {
  525. return []Completion{}
  526. }
  527. var completions []Completion
  528. flagName := "--" + flag.Name
  529. if strings.HasPrefix(flagName, toComplete) {
  530. // Flag without the =
  531. completions = append(completions, CompletionWithDesc(flagName, flag.Usage))
  532. // Why suggest both long forms: --flag and --flag= ?
  533. // This forces the user to *always* have to type either an = or a space after the flag name.
  534. // Let's be nice and avoid making users have to do that.
  535. // Since boolean flags and shortname flags don't show the = form, let's go that route and never show it.
  536. // The = form will still work, we just won't suggest it.
  537. // This also makes the list of suggested flags shorter as we avoid all the = forms.
  538. //
  539. // if len(flag.NoOptDefVal) == 0 {
  540. // // Flag requires a value, so it can be suffixed with =
  541. // flagName += "="
  542. // completions = append(completions, CompletionWithDesc(flagName, flag.Usage))
  543. // }
  544. }
  545. flagName = "-" + flag.Shorthand
  546. if len(flag.Shorthand) > 0 && strings.HasPrefix(flagName, toComplete) {
  547. completions = append(completions, CompletionWithDesc(flagName, flag.Usage))
  548. }
  549. return completions
  550. }
  551. func completeRequireFlags(finalCmd *Command, toComplete string) []Completion {
  552. var completions []Completion
  553. doCompleteRequiredFlags := func(flag *pflag.Flag) {
  554. if _, present := flag.Annotations[BashCompOneRequiredFlag]; present {
  555. if !flag.Changed {
  556. // If the flag is not already present, we suggest it as a completion
  557. completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
  558. }
  559. }
  560. }
  561. // We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands
  562. // that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and
  563. // non-inherited flags.
  564. finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
  565. doCompleteRequiredFlags(flag)
  566. })
  567. finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
  568. doCompleteRequiredFlags(flag)
  569. })
  570. return completions
  571. }
  572. func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) {
  573. if finalCmd.DisableFlagParsing {
  574. // We only do flag completion if we are allowed to parse flags
  575. // This is important for commands which have requested to do their own flag completion.
  576. return nil, args, lastArg, nil
  577. }
  578. var flagName string
  579. trimmedArgs := args
  580. flagWithEqual := false
  581. orgLastArg := lastArg
  582. // When doing completion of a flag name, as soon as an argument starts with
  583. // a '-' we know it is a flag. We cannot use isFlagArg() here as that function
  584. // requires the flag name to be complete
  585. if len(lastArg) > 0 && lastArg[0] == '-' {
  586. if index := strings.Index(lastArg, "="); index >= 0 {
  587. // Flag with an =
  588. if strings.HasPrefix(lastArg[:index], "--") {
  589. // Flag has full name
  590. flagName = lastArg[2:index]
  591. } else {
  592. // Flag is shorthand
  593. // We have to get the last shorthand flag name
  594. // e.g. `-asd` => d to provide the correct completion
  595. // https://github.com/spf13/cobra/issues/1257
  596. flagName = lastArg[index-1 : index]
  597. }
  598. lastArg = lastArg[index+1:]
  599. flagWithEqual = true
  600. } else {
  601. // Normal flag completion
  602. return nil, args, lastArg, nil
  603. }
  604. }
  605. if len(flagName) == 0 {
  606. if len(args) > 0 {
  607. prevArg := args[len(args)-1]
  608. if isFlagArg(prevArg) {
  609. // Only consider the case where the flag does not contain an =.
  610. // If the flag contains an = it means it has already been fully processed,
  611. // so we don't need to deal with it here.
  612. if index := strings.Index(prevArg, "="); index < 0 {
  613. if strings.HasPrefix(prevArg, "--") {
  614. // Flag has full name
  615. flagName = prevArg[2:]
  616. } else {
  617. // Flag is shorthand
  618. // We have to get the last shorthand flag name
  619. // e.g. `-asd` => d to provide the correct completion
  620. // https://github.com/spf13/cobra/issues/1257
  621. flagName = prevArg[len(prevArg)-1:]
  622. }
  623. // Remove the uncompleted flag or else there could be an error created
  624. // for an invalid value for that flag
  625. trimmedArgs = args[:len(args)-1]
  626. }
  627. }
  628. }
  629. }
  630. if len(flagName) == 0 {
  631. // Not doing flag completion
  632. return nil, trimmedArgs, lastArg, nil
  633. }
  634. flag := findFlag(finalCmd, flagName)
  635. if flag == nil {
  636. // Flag not supported by this command, the interspersed option might be set so return the original args
  637. return nil, args, orgLastArg, &flagCompError{subCommand: finalCmd.Name(), flagName: flagName}
  638. }
  639. if !flagWithEqual {
  640. if len(flag.NoOptDefVal) != 0 {
  641. // We had assumed dealing with a two-word flag but the flag is a boolean flag.
  642. // In that case, there is no value following it, so we are not really doing flag completion.
  643. // Reset everything to do noun completion.
  644. trimmedArgs = args
  645. flag = nil
  646. }
  647. }
  648. return flag, trimmedArgs, lastArg, nil
  649. }
  650. // InitDefaultCompletionCmd adds a default 'completion' command to c.
  651. // This function will do nothing if any of the following is true:
  652. // 1- the feature has been explicitly disabled by the program,
  653. // 2- c has no subcommands (to avoid creating one),
  654. // 3- c already has a 'completion' command provided by the program.
  655. func (c *Command) InitDefaultCompletionCmd(args ...string) {
  656. if c.CompletionOptions.DisableDefaultCmd {
  657. return
  658. }
  659. for _, cmd := range c.commands {
  660. if cmd.Name() == compCmdName || cmd.HasAlias(compCmdName) {
  661. // A completion command is already available
  662. return
  663. }
  664. }
  665. haveNoDescFlag := !c.CompletionOptions.DisableNoDescFlag && !c.CompletionOptions.DisableDescriptions
  666. // Special case to know if there are sub-commands or not.
  667. hasSubCommands := false
  668. for _, cmd := range c.commands {
  669. if cmd.Name() != ShellCompRequestCmd && cmd.Name() != helpCommandName {
  670. // We found a real sub-command (not 'help' or '__complete')
  671. hasSubCommands = true
  672. break
  673. }
  674. }
  675. completionCmd := &Command{
  676. Use: compCmdName,
  677. Short: "Generate the autocompletion script for the specified shell",
  678. Long: fmt.Sprintf(`Generate the autocompletion script for %[1]s for the specified shell.
  679. See each sub-command's help for details on how to use the generated script.
  680. `, c.Root().Name()),
  681. Args: NoArgs,
  682. ValidArgsFunction: NoFileCompletions,
  683. Hidden: c.CompletionOptions.HiddenDefaultCmd,
  684. GroupID: c.completionCommandGroupID,
  685. }
  686. c.AddCommand(completionCmd)
  687. if !hasSubCommands {
  688. // If the 'completion' command will be the only sub-command,
  689. // we only create it if it is actually being called.
  690. // This avoids breaking programs that would suddenly find themselves with
  691. // a subcommand, which would prevent them from accepting arguments.
  692. // We also create the 'completion' command if the user is triggering
  693. // shell completion for it (prog __complete completion '')
  694. subCmd, cmdArgs, err := c.Find(args)
  695. if err != nil || subCmd.Name() != compCmdName &&
  696. (subCmd.Name() != ShellCompRequestCmd || len(cmdArgs) <= 1 || cmdArgs[0] != compCmdName) {
  697. // The completion command is not being called or being completed so we remove it.
  698. c.RemoveCommand(completionCmd)
  699. return
  700. }
  701. }
  702. out := c.OutOrStdout()
  703. noDesc := c.CompletionOptions.DisableDescriptions
  704. shortDesc := "Generate the autocompletion script for %s"
  705. bash := &Command{
  706. Use: "bash",
  707. Short: fmt.Sprintf(shortDesc, "bash"),
  708. Long: fmt.Sprintf(`Generate the autocompletion script for the bash shell.
  709. This script depends on the 'bash-completion' package.
  710. If it is not installed already, you can install it via your OS's package manager.
  711. To load completions in your current shell session:
  712. source <(%[1]s completion bash)
  713. To load completions for every new session, execute once:
  714. #### Linux:
  715. %[1]s completion bash > /etc/bash_completion.d/%[1]s
  716. #### macOS:
  717. %[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s
  718. You will need to start a new shell for this setup to take effect.
  719. `, c.Root().Name()),
  720. Args: NoArgs,
  721. DisableFlagsInUseLine: true,
  722. ValidArgsFunction: NoFileCompletions,
  723. RunE: func(cmd *Command, args []string) error {
  724. return cmd.Root().GenBashCompletionV2(out, !noDesc)
  725. },
  726. }
  727. if haveNoDescFlag {
  728. bash.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
  729. }
  730. zsh := &Command{
  731. Use: "zsh",
  732. Short: fmt.Sprintf(shortDesc, "zsh"),
  733. Long: fmt.Sprintf(`Generate the autocompletion script for the zsh shell.
  734. If shell completion is not already enabled in your environment you will need
  735. to enable it. You can execute the following once:
  736. echo "autoload -U compinit; compinit" >> ~/.zshrc
  737. To load completions in your current shell session:
  738. source <(%[1]s completion zsh)
  739. To load completions for every new session, execute once:
  740. #### Linux:
  741. %[1]s completion zsh > "${fpath[1]}/_%[1]s"
  742. #### macOS:
  743. %[1]s completion zsh > $(brew --prefix)/share/zsh/site-functions/_%[1]s
  744. You will need to start a new shell for this setup to take effect.
  745. `, c.Root().Name()),
  746. Args: NoArgs,
  747. ValidArgsFunction: NoFileCompletions,
  748. RunE: func(cmd *Command, args []string) error {
  749. if noDesc {
  750. return cmd.Root().GenZshCompletionNoDesc(out)
  751. }
  752. return cmd.Root().GenZshCompletion(out)
  753. },
  754. }
  755. if haveNoDescFlag {
  756. zsh.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
  757. }
  758. fish := &Command{
  759. Use: "fish",
  760. Short: fmt.Sprintf(shortDesc, "fish"),
  761. Long: fmt.Sprintf(`Generate the autocompletion script for the fish shell.
  762. To load completions in your current shell session:
  763. %[1]s completion fish | source
  764. To load completions for every new session, execute once:
  765. %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish
  766. You will need to start a new shell for this setup to take effect.
  767. `, c.Root().Name()),
  768. Args: NoArgs,
  769. ValidArgsFunction: NoFileCompletions,
  770. RunE: func(cmd *Command, args []string) error {
  771. return cmd.Root().GenFishCompletion(out, !noDesc)
  772. },
  773. }
  774. if haveNoDescFlag {
  775. fish.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
  776. }
  777. powershell := &Command{
  778. Use: "powershell",
  779. Short: fmt.Sprintf(shortDesc, "powershell"),
  780. Long: fmt.Sprintf(`Generate the autocompletion script for powershell.
  781. To load completions in your current shell session:
  782. %[1]s completion powershell | Out-String | Invoke-Expression
  783. To load completions for every new session, add the output of the above command
  784. to your powershell profile.
  785. `, c.Root().Name()),
  786. Args: NoArgs,
  787. ValidArgsFunction: NoFileCompletions,
  788. RunE: func(cmd *Command, args []string) error {
  789. if noDesc {
  790. return cmd.Root().GenPowerShellCompletion(out)
  791. }
  792. return cmd.Root().GenPowerShellCompletionWithDesc(out)
  793. },
  794. }
  795. if haveNoDescFlag {
  796. powershell.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
  797. }
  798. completionCmd.AddCommand(bash, zsh, fish, powershell)
  799. }
  800. func findFlag(cmd *Command, name string) *pflag.Flag {
  801. flagSet := cmd.Flags()
  802. if len(name) == 1 {
  803. // First convert the short flag into a long flag
  804. // as the cmd.Flag() search only accepts long flags
  805. if short := flagSet.ShorthandLookup(name); short != nil {
  806. name = short.Name
  807. } else {
  808. set := cmd.InheritedFlags()
  809. if short = set.ShorthandLookup(name); short != nil {
  810. name = short.Name
  811. } else {
  812. return nil
  813. }
  814. }
  815. }
  816. return cmd.Flag(name)
  817. }
  818. // CompDebug prints the specified string to the same file as where the
  819. // completion script prints its logs.
  820. // Note that completion printouts should never be on stdout as they would
  821. // be wrongly interpreted as actual completion choices by the completion script.
  822. func CompDebug(msg string, printToStdErr bool) {
  823. msg = fmt.Sprintf("[Debug] %s", msg)
  824. // Such logs are only printed when the user has set the environment
  825. // variable BASH_COMP_DEBUG_FILE to the path of some file to be used.
  826. if path := os.Getenv("BASH_COMP_DEBUG_FILE"); path != "" {
  827. f, err := os.OpenFile(path,
  828. os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
  829. if err == nil {
  830. defer f.Close()
  831. WriteStringAndCheck(f, msg)
  832. }
  833. }
  834. if printToStdErr {
  835. // Must print to stderr for this not to be read by the completion script.
  836. fmt.Fprint(os.Stderr, msg)
  837. }
  838. }
  839. // CompDebugln prints the specified string with a newline at the end
  840. // to the same file as where the completion script prints its logs.
  841. // Such logs are only printed when the user has set the environment
  842. // variable BASH_COMP_DEBUG_FILE to the path of some file to be used.
  843. func CompDebugln(msg string, printToStdErr bool) {
  844. CompDebug(fmt.Sprintf("%s\n", msg), printToStdErr)
  845. }
  846. // CompError prints the specified completion message to stderr.
  847. func CompError(msg string) {
  848. msg = fmt.Sprintf("[Error] %s", msg)
  849. CompDebug(msg, true)
  850. }
  851. // CompErrorln prints the specified completion message to stderr with a newline at the end.
  852. func CompErrorln(msg string) {
  853. CompError(fmt.Sprintf("%s\n", msg))
  854. }
  855. // These values should not be changed: users will be using them explicitly.
  856. const (
  857. configEnvVarGlobalPrefix = "COBRA"
  858. configEnvVarSuffixDescriptions = "COMPLETION_DESCRIPTIONS"
  859. )
  860. var configEnvVarPrefixSubstRegexp = regexp.MustCompile(`[^A-Z0-9_]`)
  861. // configEnvVar returns the name of the program-specific configuration environment
  862. // variable. It has the format <PROGRAM>_<SUFFIX> where <PROGRAM> is the name of the
  863. // root command in upper case, with all non-ASCII-alphanumeric characters replaced by `_`.
  864. func configEnvVar(name, suffix string) string {
  865. // This format should not be changed: users will be using it explicitly.
  866. v := strings.ToUpper(fmt.Sprintf("%s_%s", name, suffix))
  867. v = configEnvVarPrefixSubstRegexp.ReplaceAllString(v, "_")
  868. return v
  869. }
  870. // getEnvConfig returns the value of the configuration environment variable
  871. // <PROGRAM>_<SUFFIX> where <PROGRAM> is the name of the root command in upper
  872. // case, with all non-ASCII-alphanumeric characters replaced by `_`.
  873. // If the value is empty or not set, the value of the environment variable
  874. // COBRA_<SUFFIX> is returned instead.
  875. func getEnvConfig(cmd *Command, suffix string) string {
  876. v := os.Getenv(configEnvVar(cmd.Root().Name(), suffix))
  877. if v == "" {
  878. v = os.Getenv(configEnvVar(configEnvVarGlobalPrefix, suffix))
  879. }
  880. return v
  881. }