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