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