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