Browse code

Update cobra vendor

- adds support for usage strings on flag errors
- adds support for arg validation

Signed-off-by: Daniel Nephin <dnephin@docker.com>

Daniel Nephin authored on 2016/05/26 06:32:04
Showing 4 changed files
... ...
@@ -135,7 +135,7 @@ clone git google.golang.org/cloud dae7e3d993bc3812a2185af60552bb6b847e52a0 https
135 135
 clone git github.com/docker/containerd 57b7c3da915ebe943bd304c00890959b191e5264
136 136
 
137 137
 # cli
138
-clone git github.com/spf13/cobra 0f866a6211e33cde2091d9290c08f6afd6c9ebbc
138
+clone git github.com/spf13/cobra acf60156558542e78c6f3695f74b0f871614ff55 https://github.com/dnephin/cobra.git
139 139
 clone git github.com/spf13/pflag cb88ea77998c3f024757528e3305022ab50b43be
140 140
 clone git github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
141 141
 
... ...
@@ -406,6 +406,42 @@ A flag can also be assigned locally which will only apply to that specific comma
406 406
 RootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")
407 407
 ```
408 408
 
409
+### Positional Arguments
410
+
411
+Validation of positional arguments can be specified using the `Args` field, which accepts
412
+one of the following values:
413
+
414
+- `NoArgs` - the command will report an error if there are any positional args.
415
+- `ArbitraryArgs` - the command will accept any args.
416
+- `OnlyValidArgs` - the command will report an error if there are any positiona 
417
+  args that are not in the `ValidArgs` list.
418
+- `MinimumNArgs(int)` - the command will report an error if there are not at 
419
+  least N positional args.
420
+- `MaximumNArgs(int)` - the command will report an error if there are more than 
421
+  N positional args.
422
+- `ExactArgs(int)` - the command will report an error if there are not 
423
+  exactly N positional args.
424
+- `RangeArgs(min, max)` - the command will report an error if the number of args 
425
+  is not between the minimum and maximum number of expected args.
426
+
427
+By default, `Args` uses the following legacy behaviour:
428
+- root commands with no subcommands can take arbitrary arguments
429
+- root commands with subcommands will do subcommand validity checking
430
+- subcommands will always accept arbitrary arguments and do no subsubcommand validity checking
431
+
432
+
433
+```go
434
+var HugoCmd = &cobra.Command{
435
+    Use:   "hugo",
436
+    Short: "Hugo is a very fast static site generator",
437
+    ValidArgs: []string{"one", "two"}
438
+    Args: cobra.OnlyValidArgs
439
+    Run: func(cmd *cobra.Command, args []string) {
440
+        // args will only have the values one, two
441
+        // or the cmd.Execute() will fail.
442
+    },
443
+}
444
+```
409 445
 
410 446
 ## Example
411 447
 
412 448
new file mode 100644
... ...
@@ -0,0 +1,98 @@
0
+package cobra
1
+
2
+import (
3
+	"fmt"
4
+)
5
+
6
+type PositionalArgs func(cmd *Command, args []string) error
7
+
8
+// Legacy arg validation has the following behaviour:
9
+// - root commands with no subcommands can take arbitrary arguments
10
+// - root commands with subcommands will do subcommand validity checking
11
+// - subcommands will always accept arbitrary arguments
12
+func legacyArgs(cmd *Command, args []string) error {
13
+	// no subcommand, always take args
14
+	if !cmd.HasSubCommands() {
15
+		return nil
16
+	}
17
+
18
+	// root command with subcommands, do subcommand checking
19
+	if !cmd.HasParent() && len(args) > 0 {
20
+		return fmt.Errorf("unknown command %q for %q%s", args[0], cmd.CommandPath(), cmd.findSuggestions(args[0]))
21
+	}
22
+	return nil
23
+}
24
+
25
+// NoArgs returns an error if any args are included
26
+func NoArgs(cmd *Command, args []string) error {
27
+	if len(args) > 0 {
28
+		return fmt.Errorf("unknown command %q for %q", args[0], cmd.CommandPath())
29
+	}
30
+	return nil
31
+}
32
+
33
+// OnlyValidArgs returns an error if any args are not in the list of ValidArgs
34
+func OnlyValidArgs(cmd *Command, args []string) error {
35
+	if len(cmd.ValidArgs) > 0 {
36
+		for _, v := range args {
37
+			if !stringInSlice(v, cmd.ValidArgs) {
38
+				return fmt.Errorf("invalid argument %q for %q%s", v, cmd.CommandPath(), cmd.findSuggestions(args[0]))
39
+			}
40
+		}
41
+	}
42
+	return nil
43
+}
44
+
45
+func stringInSlice(a string, list []string) bool {
46
+	for _, b := range list {
47
+		if b == a {
48
+			return true
49
+		}
50
+	}
51
+	return false
52
+}
53
+
54
+// ArbitraryArgs never returns an error
55
+func ArbitraryArgs(cmd *Command, args []string) error {
56
+	return nil
57
+}
58
+
59
+// MinimumNArgs returns an error if there is not at least N args
60
+func MinimumNArgs(n int) PositionalArgs {
61
+	return func(cmd *Command, args []string) error {
62
+		if len(args) < n {
63
+			return fmt.Errorf("requires at least %d arg(s), only received %d", n, len(args))
64
+		}
65
+		return nil
66
+	}
67
+}
68
+
69
+// MaximumNArgs returns an error if there are more than N args
70
+func MaximumNArgs(n int) PositionalArgs {
71
+	return func(cmd *Command, args []string) error {
72
+		if len(args) > n {
73
+			return fmt.Errorf("accepts at most %d arg(s), received %d", n, len(args))
74
+		}
75
+		return nil
76
+	}
77
+}
78
+
79
+// ExactArgs returns an error if there are not exactly n args 
80
+func ExactArgs(n int) PositionalArgs {
81
+	return func(cmd *Command, args []string) error {
82
+		if len(args) != n {
83
+			return fmt.Errorf("accepts %d arg(s), received %d", n, len(args))
84
+		}
85
+		return nil
86
+	}
87
+}
88
+
89
+// RangeArgs returns an error if the number of args is not within the expected range 
90
+func RangeArgs(min int, max int) PositionalArgs {
91
+	return func(cmd *Command, args []string) error {
92
+		if len(args) < min || len(args) > max {
93
+			return fmt.Errorf("accepts between %d and %d arg(s), received %d", min, max, len(args))
94
+		}
95
+		return nil
96
+	}
97
+}
... ...
@@ -50,6 +50,8 @@ type Command struct {
50 50
 	// List of aliases for ValidArgs. These are not suggested to the user in the bash
51 51
 	// completion, but accepted if entered manually.
52 52
 	ArgAliases []string
53
+	// Expected arguments
54
+	Args PositionalArgs
53 55
 	// Custom functions used by the bash autocompletion generator
54 56
 	BashCompletionFunction string
55 57
 	// Is this command deprecated and should print this string when used?
... ...
@@ -110,6 +112,7 @@ type Command struct {
110 110
 	output        *io.Writer               // nil means stderr; use Out() method instead
111 111
 	usageFunc     func(*Command) error     // Usage can be defined by application
112 112
 	usageTemplate string                   // Can be defined by Application
113
+	flagErrorFunc func(*Command, error) error
113 114
 	helpTemplate  string                   // Can be defined by Application
114 115
 	helpFunc      func(*Command, []string) // Help can be defined by application
115 116
 	helpCommand   *Command                 // The help command
... ...
@@ -163,6 +166,12 @@ func (c *Command) SetUsageTemplate(s string) {
163 163
 	c.usageTemplate = s
164 164
 }
165 165
 
166
+// SetFlagErrorFunc sets a function to generate an error when flag parsing
167
+// fails
168
+func (c *Command) SetFlagErrorFunc(f func(*Command, error) error) {
169
+	c.flagErrorFunc = f
170
+}
171
+
166 172
 // Can be defined by Application
167 173
 func (c *Command) SetHelpFunc(f func(*Command, []string)) {
168 174
 	c.helpFunc = f
... ...
@@ -224,6 +233,22 @@ func (c *Command) HelpFunc() func(*Command, []string) {
224 224
 	}
225 225
 }
226 226
 
227
+// FlagErrorFunc returns either the function set by SetFlagErrorFunc for this
228
+// command or a parent, or it returns a function which returns the original
229
+// error.
230
+func (c *Command) FlagErrorFunc() (f func(*Command, error) error) {
231
+	if c.flagErrorFunc != nil {
232
+		return c.flagErrorFunc
233
+	}
234
+
235
+	if c.HasParent() {
236
+		return c.parent.FlagErrorFunc()
237
+	}
238
+	return func(c *Command, err error) error {
239
+		return err
240
+	}
241
+}
242
+
227 243
 var minUsagePadding = 25
228 244
 
229 245
 func (c *Command) UsagePadding() int {
... ...
@@ -422,31 +447,27 @@ func (c *Command) Find(args []string) (*Command, []string, error) {
422 422
 	}
423 423
 
424 424
 	commandFound, a := innerfind(c, args)
425
-	argsWOflags := stripFlags(a, commandFound)
426
-
427
-	// no subcommand, always take args
428
-	if !commandFound.HasSubCommands() {
429
-		return commandFound, a, nil
425
+	if commandFound.Args == nil {
426
+		return commandFound, a, legacyArgs(commandFound, stripFlags(a, commandFound))
430 427
 	}
428
+	return commandFound, a, nil
429
+}
431 430
 
432
-	// root command with subcommands, do subcommand checking
433
-	if commandFound == c && len(argsWOflags) > 0 {
434
-		suggestionsString := ""
435
-		if !c.DisableSuggestions {
436
-			if c.SuggestionsMinimumDistance <= 0 {
437
-				c.SuggestionsMinimumDistance = 2
438
-			}
439
-			if suggestions := c.SuggestionsFor(argsWOflags[0]); len(suggestions) > 0 {
440
-				suggestionsString += "\n\nDid you mean this?\n"
441
-				for _, s := range suggestions {
442
-					suggestionsString += fmt.Sprintf("\t%v\n", s)
443
-				}
444
-			}
431
+func (c *Command) findSuggestions(arg string) string {
432
+	if c.DisableSuggestions {
433
+		return ""
434
+	}
435
+	if c.SuggestionsMinimumDistance <= 0 {
436
+		c.SuggestionsMinimumDistance = 2
437
+	}
438
+	suggestionsString := ""
439
+	if suggestions := c.SuggestionsFor(arg); len(suggestions) > 0 {
440
+		suggestionsString += "\n\nDid you mean this?\n"
441
+		for _, s := range suggestions {
442
+			suggestionsString += fmt.Sprintf("\t%v\n", s)
445 443
 		}
446
-		return commandFound, a, fmt.Errorf("unknown command %q for %q%s", argsWOflags[0], commandFound.CommandPath(), suggestionsString)
447 444
 	}
448
-
449
-	return commandFound, a, nil
445
+	return suggestionsString
450 446
 }
451 447
 
452 448
 func (c *Command) SuggestionsFor(typedName string) []string {
... ...
@@ -520,7 +541,7 @@ func (c *Command) execute(a []string) (err error) {
520 520
 
521 521
 	err = c.ParseFlags(a)
522 522
 	if err != nil {
523
-		return err
523
+		return c.FlagErrorFunc()(c, err)
524 524
 	}
525 525
 	// If help is called, regardless of other flags, return we want help
526 526
 	// Also say we need help if the command isn't runnable.
... ...
@@ -535,6 +556,10 @@ func (c *Command) execute(a []string) (err error) {
535 535
 		return flag.ErrHelp
536 536
 	}
537 537
 
538
+	if err := c.ValidateArgs(a); err != nil {
539
+		return err
540
+	}
541
+
538 542
 	c.preRun()
539 543
 	argWoFlags := c.Flags().Args()
540 544
 
... ...
@@ -673,7 +698,15 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
673 673
 	return cmd, nil
674 674
 }
675 675
 
676
+func (c *Command) ValidateArgs(args []string) error {
677
+	if c.Args == nil {
678
+		return nil
679
+	}
680
+	return c.Args(c, stripFlags(args, c))
681
+}
682
+
676 683
 func (c *Command) initHelpFlag() {
684
+	c.mergePersistentFlags()
677 685
 	if c.Flags().Lookup("help") == nil {
678 686
 		c.Flags().BoolP("help", "h", false, "help for "+c.Name())
679 687
 	}
... ...
@@ -747,7 +780,7 @@ func (c *Command) AddCommand(cmds ...*Command) {
747 747
 	}
748 748
 }
749 749
 
750
-// AddCommand removes one or more commands from a parent command.
750
+// RemoveCommand removes one or more commands from a parent command.
751 751
 func (c *Command) RemoveCommand(cmds ...*Command) {
752 752
 	commands := []*Command{}
753 753
 main: