Allow commands to explicitly state if they do, or do not take arbitrary arguments

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.
This commit is contained in:
Eric Paris
2015-06-09 14:17:58 -04:00
committed by Albert Nigmatzianov
parent 715f41bd7a
commit d89c499964
4 changed files with 157 additions and 22 deletions

View File

@ -27,6 +27,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.
// E.g. 'go run ...' - 'run' is the command. Cobra requires
// you to define the usage and description as part of your command
@ -59,6 +68,8 @@ type Command struct {
// but accepted if entered manually.
ArgAliases []string
// Does this command take arbitrary arguments
TakesArgs Args
// BashCompletionFunction is custom functions used by the bash autocompletion generator.
BashCompletionFunction string
@ -472,6 +483,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) {
@ -515,31 +535,53 @@ 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%s", argsWOflags[0], commandFound.CommandPath(), c.findSuggestions(argsWOflags))
}
return commandFound, a, nil
}
// root command with subcommands, do subcommand checking
if commandFound == c && len(argsWOflags) > 0 {
suggestionsString := ""
if !c.DisableSuggestions {
if c.SuggestionsMinimumDistance <= 0 {
c.SuggestionsMinimumDistance = 2
}
if suggestions := c.SuggestionsFor(argsWOflags[0]); len(suggestions) > 0 {
suggestionsString += "\n\nDid you mean this?\n"
for _, s := range suggestions {
suggestionsString += fmt.Sprintf("\t%v\n", s)
}
}
}
return commandFound, a, fmt.Errorf("unknown command %q for %q%s", argsWOflags[0], commandFound.CommandPath(), suggestionsString)
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%s", v, commandFound.CommandPath(), c.findSuggestions(argsWOflags))
}
}
}
return commandFound, a, nil
}
func (c *Command) findSuggestions(argsWOflags []string) string {
if c.DisableSuggestions {
return ""
}
if c.SuggestionsMinimumDistance <= 0 {
c.SuggestionsMinimumDistance = 2
}
suggestionsString := ""
if suggestions := c.SuggestionsFor(argsWOflags[0]); len(suggestions) > 0 {
suggestionsString += "\n\nDid you mean this?\n"
for _, s := range suggestions {
suggestionsString += fmt.Sprintf("\t%v\n", s)
}
}
return suggestionsString
}
// SuggestionsFor provides suggestions for the typedName.
func (c *Command) SuggestionsFor(typedName string) []string {
suggestions := []string{}