aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/mitchellh/cli/cli.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/mitchellh/cli/cli.go')
-rw-r--r--vendor/github.com/mitchellh/cli/cli.go715
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 @@
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
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.
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 {
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
481func (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.
569func (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
606func (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.
705const defaultAutocompleteInstall = "autocomplete-install"
706const defaultAutocompleteUninstall = "autocomplete-uninstall"
707
708const defaultHelpTemplate = `
709{{.Help}}{{if gt (len .Subcommands) 0}}
710
711Subcommands:
712{{- range $value := .Subcommands }}
713 {{ $value.NameAligned }} {{ $value.Synopsis }}{{ end }}
714{{- end }}
715`