mirror of
https://github.com/spf13/cobra
synced 2025-05-05 12:57:22 +00:00
Merge 2188acea42
into b28774dd68
This commit is contained in:
commit
ff7168ade4
4 changed files with 141 additions and 18 deletions
32
README.md
32
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.
|
||||
|
|
|
@ -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")
|
||||
|
@ -75,10 +71,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")`)
|
||||
// check for subdirs_in_dir flags
|
||||
check(t, str, `flags_completion+=("__handle_subdirs_in_dir_flag themes")`)
|
||||
|
|
|
@ -79,6 +79,7 @@ var cmdDeprecated = &Command{
|
|||
Deprecated: "Please use echo instead",
|
||||
Run: func(cmd *Command, args []string) {
|
||||
},
|
||||
TakesArgs: None,
|
||||
}
|
||||
|
||||
var cmdTimes = &Command{
|
||||
|
@ -91,6 +92,8 @@ var cmdTimes = &Command{
|
|||
Run: func(cmd *Command, args []string) {
|
||||
tt = args
|
||||
},
|
||||
TakesArgs: ValidOnly,
|
||||
ValidArgs: []string{"one", "two", "three", "four"},
|
||||
}
|
||||
|
||||
var cmdRootNoRun = &Command{
|
||||
|
@ -103,9 +106,20 @@ var cmdRootNoRun = &Command{
|
|||
}
|
||||
|
||||
var cmdRootSameName = &Command{
|
||||
Use: "print",
|
||||
Short: "Root with the same name as a subcommand",
|
||||
Long: "The root description for help",
|
||||
Use: "print",
|
||||
Short: "Root with the same name as a subcommand",
|
||||
Long: "The root description for help",
|
||||
TakesArgs: None,
|
||||
}
|
||||
|
||||
var cmdRootTakesArgs = &Command{
|
||||
Use: "root-with-args [random args]",
|
||||
Short: "The root can run it's own function and takes args!",
|
||||
Long: "The root description for help, and some args",
|
||||
Run: func(cmd *Command, args []string) {
|
||||
tr = args
|
||||
},
|
||||
TakesArgs: Arbitrary,
|
||||
}
|
||||
|
||||
var cmdRootWithRun = &Command{
|
||||
|
@ -415,6 +429,51 @@ func TestGrandChildSameName(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRootTakesNoArgs(t *testing.T) {
|
||||
c := initializeWithSameName()
|
||||
c.AddCommand(cmdPrint, cmdEcho)
|
||||
result := simpleTester(c, "illegal")
|
||||
|
||||
expectedError := `unknown command "illegal" for "print"`
|
||||
if !strings.Contains(result.Error.Error(), expectedError) {
|
||||
t.Errorf("exptected %v, got %v", expectedError, result.Error.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRootTakesArgs(t *testing.T) {
|
||||
c := cmdRootTakesArgs
|
||||
result := simpleTester(c, "legal")
|
||||
|
||||
if result.Error != nil {
|
||||
t.Errorf("expected no error, but got %v", result.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubCmdTakesNoArgs(t *testing.T) {
|
||||
result := fullSetupTest("deprecated illegal")
|
||||
|
||||
expectedError := `unknown command "illegal" for "cobra-test deprecated"`
|
||||
if !strings.Contains(result.Error.Error(), expectedError) {
|
||||
t.Errorf("expected %v, got %v", expectedError, result.Error.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubCmdTakesArgs(t *testing.T) {
|
||||
noRRSetupTest("echo times one two")
|
||||
if strings.Join(tt, " ") != "one two" {
|
||||
t.Error("Command didn't parse correctly")
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
|
@ -590,9 +649,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)
|
||||
}
|
||||
|
||||
|
|
46
command.go
46
command.go
|
@ -28,6 +28,15 @@ import (
|
|||
flag "github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
type Args int
|
||||
|
||||
const (
|
||||
Legacy Args = iota
|
||||
Arbitrary
|
||||
ValidOnly
|
||||
None
|
||||
)
|
||||
|
||||
// Command is just that, a command for your application.
|
||||
// eg. 'go run' ... 'run' is the command. Cobra requires
|
||||
// you to define the usage and description as part of your command
|
||||
|
@ -47,6 +56,8 @@ type Command struct {
|
|||
Example string
|
||||
// List of all valid non-flag arguments, used for bash completions *TODO* actually validate these
|
||||
ValidArgs []string
|
||||
// Does this command take arbitrary arguments
|
||||
TakesArgs Args
|
||||
// Custom functions used by the bash autocompletion generator
|
||||
BashCompletionFunction string
|
||||
// Is this command deprecated and should print this string when used?
|
||||
|
@ -374,6 +385,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) {
|
||||
|
@ -417,15 +437,33 @@ func (c *Command) Find(args []string) (*Command, []string, error) {
|
|||
commandFound, a := innerfind(c, args)
|
||||
argsWOflags := stripFlags(a, commandFound)
|
||||
|
||||
// no subcommand, always take args
|
||||
if !commandFound.HasSubCommands() {
|
||||
// "Legacy" has some 'odd' characteristics.
|
||||
// - root commands with no subcommands can take arbitrary arguments
|
||||
// - root commands with subcommands will do subcommand validity checking
|
||||
// - subcommands will always accept arbitrary arguments
|
||||
if commandFound.TakesArgs == Legacy {
|
||||
// no subcommand, always take args
|
||||
if !commandFound.HasSubCommands() {
|
||||
return commandFound, a, nil
|
||||
}
|
||||
// root command with subcommands, do subcommand checking
|
||||
if commandFound == c && len(argsWOflags) > 0 {
|
||||
return commandFound, a, fmt.Errorf("unknown command %q for %q", argsWOflags[0], commandFound.CommandPath())
|
||||
}
|
||||
return commandFound, a, nil
|
||||
}
|
||||
// root command with subcommands, do subcommand checking
|
||||
if commandFound == c && len(argsWOflags) > 0 {
|
||||
|
||||
if commandFound.TakesArgs == None && len(argsWOflags) > 0 {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue