]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/mitchellh/cli/cli.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / mitchellh / cli / cli.go
CommitLineData
15c0b25d
AP
1package cli
2
3import (
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//
47type 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
107c1cdb 90 // library. This library supports bash, zsh and fish. To add support
15c0b25d
AP
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.
145func 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.
157func (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.
164func (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.
170func (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"
268func (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.
275func (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.
282func (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
301func (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
385func (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.
419func (c *CLI) initAutocompleteSub(prefix string) complete.Command {
420 var cmd complete.Command
421 walkFn := func(k string, raw interface{}) bool {
107c1cdb
ND
422 // Ignore the empty key which can be present for default commands.
423 if k == "" {
424 return false
425 }
426
15c0b25d
AP
427 // Keep track of the full key so that we can nest further if necessary
428 fullKey := k
429
430 if len(prefix) > 0 {
431 // If we have a prefix, trim the prefix + 1 (for the space)
432 // Example: turns "sub one" to "one" with prefix "sub"
433 k = k[len(prefix)+1:]
434 }
435
436 if idx := strings.Index(k, " "); idx >= 0 {
437 // If there is a space, we trim up to the space. This turns
438 // "sub sub2 sub3" into "sub". The prefix trim above will
439 // trim our current depth properly.
440 k = k[:idx]
441 }
442
443 if _, ok := cmd.Sub[k]; ok {
444 // If we already tracked this subcommand then ignore
445 return false
446 }
447
448 // If the command is hidden, don't record it at all
449 if _, ok := c.commandHidden[fullKey]; ok {
450 return false
451 }
452
453 if cmd.Sub == nil {
454 cmd.Sub = complete.Commands(make(map[string]complete.Command))
455 }
456 subCmd := c.initAutocompleteSub(fullKey)
457
458 // Instantiate the command so that we can check if the command is
459 // a CommandAutocomplete implementation. If there is an error
460 // creating the command, we just ignore it since that will be caught
461 // later.
462 impl, err := raw.(CommandFactory)()
463 if err != nil {
464 impl = nil
465 }
466
467 // Check if it implements ComandAutocomplete. If so, setup the autocomplete
468 if c, ok := impl.(CommandAutocomplete); ok {
469 subCmd.Args = c.AutocompleteArgs()
470 subCmd.Flags = c.AutocompleteFlags()
471 }
472
473 cmd.Sub[k] = subCmd
474 return false
475 }
476
477 walkPrefix := prefix
478 if walkPrefix != "" {
479 walkPrefix += " "
480 }
481
482 c.commandTree.WalkPrefix(walkPrefix, walkFn)
483 return cmd
484}
485
486func (c *CLI) commandHelp(command Command) {
487 // Get the template to use
488 tpl := strings.TrimSpace(defaultHelpTemplate)
489 if t, ok := command.(CommandHelpTemplate); ok {
490 tpl = t.HelpTemplate()
491 }
492 if !strings.HasSuffix(tpl, "\n") {
493 tpl += "\n"
494 }
495
496 // Parse it
497 t, err := template.New("root").Parse(tpl)
498 if err != nil {
499 t = template.Must(template.New("root").Parse(fmt.Sprintf(
500 "Internal error! Failed to parse command help template: %s\n", err)))
501 }
502
503 // Template data
504 data := map[string]interface{}{
505 "Name": c.Name,
506 "Help": command.Help(),
507 }
508
509 // Build subcommand list if we have it
510 var subcommandsTpl []map[string]interface{}
511 if c.commandNested {
512 // Get the matching keys
513 subcommands := c.helpCommands(c.Subcommand())
514 keys := make([]string, 0, len(subcommands))
515 for k := range subcommands {
516 keys = append(keys, k)
517 }
518
519 // Sort the keys
520 sort.Strings(keys)
521
522 // Figure out the padding length
523 var longest int
524 for _, k := range keys {
525 if v := len(k); v > longest {
526 longest = v
527 }
528 }
529
530 // Go through and create their structures
531 subcommandsTpl = make([]map[string]interface{}, 0, len(subcommands))
532 for _, k := range keys {
533 // Get the command
534 raw, ok := subcommands[k]
535 if !ok {
536 c.HelpWriter.Write([]byte(fmt.Sprintf(
537 "Error getting subcommand %q", k)))
538 }
539 sub, err := raw()
540 if err != nil {
541 c.HelpWriter.Write([]byte(fmt.Sprintf(
542 "Error instantiating %q: %s", k, err)))
543 }
544
545 // Find the last space and make sure we only include that last part
546 name := k
547 if idx := strings.LastIndex(k, " "); idx > -1 {
548 name = name[idx+1:]
549 }
550
551 subcommandsTpl = append(subcommandsTpl, map[string]interface{}{
552 "Name": name,
553 "NameAligned": name + strings.Repeat(" ", longest-len(k)),
554 "Help": sub.Help(),
555 "Synopsis": sub.Synopsis(),
556 })
557 }
558 }
559 data["Subcommands"] = subcommandsTpl
560
561 // Write
562 err = t.Execute(c.HelpWriter, data)
563 if err == nil {
564 return
565 }
566
567 // An error, just output...
568 c.HelpWriter.Write([]byte(fmt.Sprintf(
569 "Internal error rendering help: %s", err)))
570}
571
572// helpCommands returns the subcommands for the HelpFunc argument.
573// This will only contain immediate subcommands.
574func (c *CLI) helpCommands(prefix string) map[string]CommandFactory {
575 // If our prefix isn't empty, make sure it ends in ' '
576 if prefix != "" && prefix[len(prefix)-1] != ' ' {
577 prefix += " "
578 }
579
580 // Get all the subkeys of this command
581 var keys []string
582 c.commandTree.WalkPrefix(prefix, func(k string, raw interface{}) bool {
583 // Ignore any sub-sub keys, i.e. "foo bar baz" when we want "foo bar"
584 if !strings.Contains(k[len(prefix):], " ") {
585 keys = append(keys, k)
586 }
587
588 return false
589 })
590
591 // For each of the keys return that in the map
592 result := make(map[string]CommandFactory, len(keys))
593 for _, k := range keys {
594 raw, ok := c.commandTree.Get(k)
595 if !ok {
596 // We just got it via WalkPrefix above, so we just panic
597 panic("not found: " + k)
598 }
599
600 // If this is a hidden command, don't show it
601 if _, ok := c.commandHidden[k]; ok {
602 continue
603 }
604
605 result[k] = raw.(CommandFactory)
606 }
607
608 return result
609}
610
611func (c *CLI) processArgs() {
612 for i, arg := range c.Args {
613 if arg == "--" {
614 break
615 }
616
617 // Check for help flags.
618 if arg == "-h" || arg == "-help" || arg == "--help" {
619 c.isHelp = true
620 continue
621 }
622
623 // Check for autocomplete flags
624 if c.Autocomplete {
625 if arg == "-"+c.AutocompleteInstall || arg == "--"+c.AutocompleteInstall {
626 c.isAutocompleteInstall = true
627 continue
628 }
629
630 if arg == "-"+c.AutocompleteUninstall || arg == "--"+c.AutocompleteUninstall {
631 c.isAutocompleteUninstall = true
632 continue
633 }
634 }
635
636 if c.subcommand == "" {
637 // Check for version flags if not in a subcommand.
638 if arg == "-v" || arg == "-version" || arg == "--version" {
639 c.isVersion = true
640 continue
641 }
642
643 if arg != "" && arg[0] == '-' {
644 // Record the arg...
645 c.topFlags = append(c.topFlags, arg)
646 }
647 }
648
649 // If we didn't find a subcommand yet and this is the first non-flag
650 // argument, then this is our subcommand.
651 if c.subcommand == "" && arg != "" && arg[0] != '-' {
652 c.subcommand = arg
653 if c.commandNested {
654 // If the command has a space in it, then it is invalid.
655 // Set a blank command so that it fails.
656 if strings.ContainsRune(arg, ' ') {
657 c.subcommand = ""
658 return
659 }
660
661 // Determine the argument we look to to end subcommands.
662 // We look at all arguments until one has a space. This
663 // disallows commands like: ./cli foo "bar baz". An argument
664 // with a space is always an argument.
665 j := 0
666 for k, v := range c.Args[i:] {
667 if strings.ContainsRune(v, ' ') {
668 break
669 }
670
671 j = i + k + 1
672 }
673
674 // Nested CLI, the subcommand is actually the entire
675 // arg list up to a flag that is still a valid subcommand.
676 searchKey := strings.Join(c.Args[i:j], " ")
677 k, _, ok := c.commandTree.LongestPrefix(searchKey)
678 if ok {
679 // k could be a prefix that doesn't contain the full
680 // command such as "foo" instead of "foobar", so we
681 // need to verify that we have an entire key. To do that,
682 // we look for an ending in a space or an end of string.
683 reVerify := regexp.MustCompile(regexp.QuoteMeta(k) + `( |$)`)
684 if reVerify.MatchString(searchKey) {
685 c.subcommand = k
686 i += strings.Count(k, " ")
687 }
688 }
689 }
690
691 // The remaining args the subcommand arguments
692 c.subcommandArgs = c.Args[i+1:]
693 }
694 }
695
696 // If we never found a subcommand and support a default command, then
697 // switch to using that.
698 if c.subcommand == "" {
699 if _, ok := c.Commands[""]; ok {
700 args := c.topFlags
701 args = append(args, c.subcommandArgs...)
702 c.topFlags = nil
703 c.subcommandArgs = args
704 }
705 }
706}
707
708// defaultAutocompleteInstall and defaultAutocompleteUninstall are the
709// default values for the autocomplete install and uninstall flags.
710const defaultAutocompleteInstall = "autocomplete-install"
711const defaultAutocompleteUninstall = "autocomplete-uninstall"
712
713const defaultHelpTemplate = `
714{{.Help}}{{if gt (len .Subcommands) 0}}
715
716Subcommands:
717{{- range $value := .Subcommands }}
718 {{ $value.NameAligned }} {{ $value.Synopsis }}{{ end }}
719{{- end }}
720`