From 2188acea42d3c43eb7139df45f86efce447fcde5 Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Tue, 9 Jun 2015 19:17:01 -0400 Subject: [PATCH] Check that arguments are in ValidArgs If a command defined cmd.ValidArgs check that the argument is actually in ValidArgs and fail if it is not. --- README.md | 32 ++++++++++++++++++++++++++++++++ bash_completions_test.go | 12 +++--------- cobra_test.go | 18 ++++++++++++++---- command.go | 17 +++++++++++++++++ 4 files changed, 66 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index b1fb0889..91af2d86 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,38 @@ A flag can also be assigned locally which will only apply to that specific comma HugoCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") +### Specify if you command takes arguments + +There are multiple options for how a command can handle unknown arguments which can be set in `TakesArgs` +- `Legacy` +- `None` +- `Arbitrary` +- `ValidOnly` + +`Legacy` (or default) the rules are as follows: +- root commands with no subcommands can take arbitrary arguments +- root commands with subcommands will do subcommand validity checking +- subcommands will always accept arbitrary arguments and do no subsubcommand validity checking + +`None` the command will be rejected if there are any left over arguments after parsing flags. + +`Arbitrary` any additional values left after parsing flags will be passed in to your `Run` function. + +`ValidOnly` you must define all valid (non-subcommand) arguments to your command. These are defined in a slice name ValidArgs. For example a command which only takes the argument "one" or "two" would be defined as: + +```go +var HugoCmd = &cobra.Command{ + Use: "hugo", + Short: "Hugo is a very fast static site generator", + ValidArgs: []string{"one", "two", "three", "four"} + TakesArgs: cobra.ValidOnly + Run: func(cmd *cobra.Command, args []string) { + // args will only have the values one, two, three, four + // or the cmd.Execute() will fail. + }, + } +``` + ### Remove a command from its parent Removing a command is not a common action in simple programs but it allows 3rd parties to customize an existing command tree. diff --git a/bash_completions_test.go b/bash_completions_test.go index acb6d81b..c9d90402 100644 --- a/bash_completions_test.go +++ b/bash_completions_test.go @@ -42,11 +42,7 @@ func TestBashCompletions(t *testing.T) { // required flag c.MarkFlagRequired("introot") - // valid nouns - validArgs := []string{"pods", "nodes", "services", "replicationControllers"} - c.ValidArgs = validArgs - - // filename + // filename extentions var flagval string c.Flags().StringVar(&flagval, "filename", "", "Enter a filename") c.MarkFlagFilename("filename", "json", "yaml", "yml") @@ -70,10 +66,8 @@ func TestBashCompletions(t *testing.T) { // check for custom completion function check(t, str, `COMPREPLY=( "hello" )`) // check for required nouns - check(t, str, `must_have_one_noun+=("pods")`) - // check for filename extension flags - check(t, str, `flags_completion+=("_filedir")`) - // check for filename extension flags + check(t, str, `must_have_one_noun+=("three")`) + // check for filename extention flags check(t, str, `flags_completion+=("__handle_filename_extension_flag json|yaml|yml")`) checkOmit(t, str, cmdDeprecated.Name()) diff --git a/cobra_test.go b/cobra_test.go index 625ed28d..491aa20d 100644 --- a/cobra_test.go +++ b/cobra_test.go @@ -81,7 +81,8 @@ var cmdTimes = &Command{ Run: func(cmd *Command, args []string) { tt = args }, - TakesArgs: Arbitrary, + TakesArgs: ValidOnly, + ValidArgs: []string{"one", "two", "three", "four"}, } var cmdRootNoRun = &Command{ @@ -434,7 +435,7 @@ func TestSubCmdTakesNoArgs(t *testing.T) { expectedError := `unknown command "illegal" for "cobra-test deprecated"` if !strings.Contains(result.Error.Error(), expectedError) { - t.Errorf("exptected %v, got %v", expectedError, result.Error.Error()) + t.Errorf("expected %v, got %v", expectedError, result.Error.Error()) } } @@ -445,6 +446,15 @@ func TestSubCmdTakesArgs(t *testing.T) { } } +func TestCmdOnlyValidArgs(t *testing.T) { + result := noRRSetupTest("echo times one two five") + + expectedError := `invalid argument "five"` + if !strings.Contains(result.Error.Error(), expectedError) { + t.Errorf("expected %v, got %v", expectedError, result.Error.Error()) + } +} + func TestFlagLong(t *testing.T) { noRRSetupTest("echo --intone=13 something here") @@ -620,9 +630,9 @@ func TestPersistentFlags(t *testing.T) { } // persistentFlag should act like normal flag on it's own command - fullSetupTest("echo times -s again -c -p test here") + fullSetupTest("echo times -s again -c -p one two") - if strings.Join(tt, " ") != "test here" { + if strings.Join(tt, " ") != "one two" { t.Errorf("flags didn't leave proper args remaining..%s given", tt) } diff --git a/command.go b/command.go index fff21435..192718c2 100644 --- a/command.go +++ b/command.go @@ -33,6 +33,7 @@ type Args int const ( Legacy Args = iota Arbitrary + ValidOnly None ) @@ -383,6 +384,15 @@ func argsMinusFirstX(args []string, x string) []string { return args } +func stringInSlice(a string, list []string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} + // find the target command given the args and command tree // Meant to be run on the highest node. Only searches down. func (c *Command) Find(args []string) (*Command, []string, error) { @@ -446,6 +456,13 @@ func (c *Command) Find(args []string) (*Command, []string, error) { return commandFound, a, fmt.Errorf("unknown command %q for %q", argsWOflags[0], commandFound.CommandPath()) } + if commandFound.TakesArgs == ValidOnly && len(commandFound.ValidArgs) > 0 { + for _, v := range argsWOflags { + if !stringInSlice(v, commandFound.ValidArgs) { + return commandFound, a, fmt.Errorf("invalid argument %q for %q", v, commandFound.CommandPath()) + } + } + } return commandFound, a, nil }