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