Support for case-insensitive command names (#1802)
Add a global `EnableCaseInsensitive` variable to allow case-insensitive command names. The variable supports commands names and aliases globally. Resolves #1382
This commit is contained in:
parent
70e53f62be
commit
d689184a42
13
cobra.go
13
cobra.go
@ -40,14 +40,23 @@ var templateFuncs = template.FuncMap{
|
||||
|
||||
var initializers []func()
|
||||
|
||||
const (
|
||||
defaultPrefixMatching = false
|
||||
defaultCommandSorting = true
|
||||
defaultCaseInsensitive = false
|
||||
)
|
||||
|
||||
// EnablePrefixMatching allows to set automatic prefix matching. Automatic prefix matching can be a dangerous thing
|
||||
// to automatically enable in CLI tools.
|
||||
// Set this to true to enable it.
|
||||
var EnablePrefixMatching = false
|
||||
var EnablePrefixMatching = defaultPrefixMatching
|
||||
|
||||
// EnableCommandSorting controls sorting of the slice of commands, which is turned on by default.
|
||||
// To disable sorting, set it to false.
|
||||
var EnableCommandSorting = true
|
||||
var EnableCommandSorting = defaultCommandSorting
|
||||
|
||||
// EnableCaseInsensitive allows case-insensitive commands names. (case sensitive by default)
|
||||
var EnableCaseInsensitive = defaultCaseInsensitive
|
||||
|
||||
// MousetrapHelpText enables an information splash screen on Windows
|
||||
// if the CLI is started from explorer.exe.
|
||||
|
15
command.go
15
command.go
@ -676,7 +676,7 @@ func (c *Command) findSuggestions(arg string) string {
|
||||
func (c *Command) findNext(next string) *Command {
|
||||
matches := make([]*Command, 0)
|
||||
for _, cmd := range c.commands {
|
||||
if cmd.Name() == next || cmd.HasAlias(next) {
|
||||
if commandNameMatches(cmd.Name(), next) || cmd.HasAlias(next) {
|
||||
cmd.commandCalledAs.name = next
|
||||
return cmd
|
||||
}
|
||||
@ -1328,7 +1328,7 @@ func (c *Command) Name() string {
|
||||
// HasAlias determines if a given string is an alias of the command.
|
||||
func (c *Command) HasAlias(s string) bool {
|
||||
for _, a := range c.Aliases {
|
||||
if a == s {
|
||||
if commandNameMatches(a, s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -1695,3 +1695,14 @@ func (c *Command) updateParentsPflags() {
|
||||
c.parentsPflags.AddFlagSet(parent.PersistentFlags())
|
||||
})
|
||||
}
|
||||
|
||||
// commandNameMatches checks if two command names are equal
|
||||
// taking into account case sensitivity according to
|
||||
// EnableCaseInsensitive global configuration.
|
||||
func commandNameMatches(s string, t string) bool {
|
||||
if EnableCaseInsensitive {
|
||||
return strings.EqualFold(s, t)
|
||||
}
|
||||
|
||||
return s == t
|
||||
}
|
||||
|
115
command_test.go
115
command_test.go
@ -314,7 +314,7 @@ func TestEnablePrefixMatching(t *testing.T) {
|
||||
t.Errorf("aCmdArgs expected: %q, got: %q", onetwo, got)
|
||||
}
|
||||
|
||||
EnablePrefixMatching = false
|
||||
EnablePrefixMatching = defaultPrefixMatching
|
||||
}
|
||||
|
||||
func TestAliasPrefixMatching(t *testing.T) {
|
||||
@ -349,7 +349,7 @@ func TestAliasPrefixMatching(t *testing.T) {
|
||||
t.Errorf("timesCmdArgs expected: %v, got: %v", onetwo, got)
|
||||
}
|
||||
|
||||
EnablePrefixMatching = false
|
||||
EnablePrefixMatching = defaultPrefixMatching
|
||||
}
|
||||
|
||||
// TestChildSameName checks the correct behaviour of cobra in cases,
|
||||
@ -1263,6 +1263,113 @@ func TestSuggestions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaseInsensitive(t *testing.T) {
|
||||
rootCmd := &Command{Use: "root", Run: emptyRun}
|
||||
childCmd := &Command{Use: "child", Run: emptyRun, Aliases: []string{"alternative"}}
|
||||
granchildCmd := &Command{Use: "GRANDCHILD", Run: emptyRun, Aliases: []string{"ALIAS"}}
|
||||
|
||||
childCmd.AddCommand(granchildCmd)
|
||||
rootCmd.AddCommand(childCmd)
|
||||
|
||||
tests := []struct {
|
||||
args []string
|
||||
failWithoutEnabling bool
|
||||
}{
|
||||
{
|
||||
args: []string{"child"},
|
||||
failWithoutEnabling: false,
|
||||
},
|
||||
{
|
||||
args: []string{"CHILD"},
|
||||
failWithoutEnabling: true,
|
||||
},
|
||||
{
|
||||
args: []string{"chILD"},
|
||||
failWithoutEnabling: true,
|
||||
},
|
||||
{
|
||||
args: []string{"CHIld"},
|
||||
failWithoutEnabling: true,
|
||||
},
|
||||
{
|
||||
args: []string{"alternative"},
|
||||
failWithoutEnabling: false,
|
||||
},
|
||||
{
|
||||
args: []string{"ALTERNATIVE"},
|
||||
failWithoutEnabling: true,
|
||||
},
|
||||
{
|
||||
args: []string{"ALTernatIVE"},
|
||||
failWithoutEnabling: true,
|
||||
},
|
||||
{
|
||||
args: []string{"alternatiVE"},
|
||||
failWithoutEnabling: true,
|
||||
},
|
||||
{
|
||||
args: []string{"child", "GRANDCHILD"},
|
||||
failWithoutEnabling: false,
|
||||
},
|
||||
{
|
||||
args: []string{"child", "grandchild"},
|
||||
failWithoutEnabling: true,
|
||||
},
|
||||
{
|
||||
args: []string{"CHIld", "GRANdchild"},
|
||||
failWithoutEnabling: true,
|
||||
},
|
||||
{
|
||||
args: []string{"alternative", "ALIAS"},
|
||||
failWithoutEnabling: false,
|
||||
},
|
||||
{
|
||||
args: []string{"alternative", "alias"},
|
||||
failWithoutEnabling: true,
|
||||
},
|
||||
{
|
||||
args: []string{"CHILD", "alias"},
|
||||
failWithoutEnabling: true,
|
||||
},
|
||||
{
|
||||
args: []string{"CHIld", "aliAS"},
|
||||
failWithoutEnabling: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
for _, enableCaseInsensitivity := range []bool{true, false} {
|
||||
EnableCaseInsensitive = enableCaseInsensitivity
|
||||
|
||||
output, err := executeCommand(rootCmd, test.args...)
|
||||
expectedFailure := test.failWithoutEnabling && !enableCaseInsensitivity
|
||||
|
||||
if !expectedFailure && output != "" {
|
||||
t.Errorf("Unexpected output: %v", output)
|
||||
}
|
||||
if !expectedFailure && err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EnableCaseInsensitive = defaultCaseInsensitive
|
||||
}
|
||||
|
||||
// This test make sure we keep backwards-compatibility with respect
|
||||
// to command names case sensitivity behavior.
|
||||
func TestCaseSensitivityBackwardCompatibility(t *testing.T) {
|
||||
rootCmd := &Command{Use: "root", Run: emptyRun}
|
||||
childCmd := &Command{Use: "child", Run: emptyRun}
|
||||
|
||||
rootCmd.AddCommand(childCmd)
|
||||
_, err := executeCommand(rootCmd, strings.ToUpper(childCmd.Use))
|
||||
if err == nil {
|
||||
t.Error("Expected error on calling a command in upper case while command names are case sensitive. Got nil.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRemoveCommand(t *testing.T) {
|
||||
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
||||
childCmd := &Command{Use: "child", Run: emptyRun}
|
||||
@ -1622,7 +1729,7 @@ func TestCommandsAreSorted(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
EnableCommandSorting = true
|
||||
EnableCommandSorting = defaultCommandSorting
|
||||
}
|
||||
|
||||
func TestEnableCommandSortingIsDisabled(t *testing.T) {
|
||||
@ -1643,7 +1750,7 @@ func TestEnableCommandSortingIsDisabled(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
EnableCommandSorting = true
|
||||
EnableCommandSorting = defaultCommandSorting
|
||||
}
|
||||
|
||||
func TestSetOutput(t *testing.T) {
|
||||
|
Loading…
Reference in New Issue
Block a user