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()
|
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
|
// EnablePrefixMatching allows to set automatic prefix matching. Automatic prefix matching can be a dangerous thing
|
||||||
// to automatically enable in CLI tools.
|
// to automatically enable in CLI tools.
|
||||||
// Set this to true to enable it.
|
// 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.
|
// EnableCommandSorting controls sorting of the slice of commands, which is turned on by default.
|
||||||
// To disable sorting, set it to false.
|
// 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
|
// MousetrapHelpText enables an information splash screen on Windows
|
||||||
// if the CLI is started from explorer.exe.
|
// 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 {
|
func (c *Command) findNext(next string) *Command {
|
||||||
matches := make([]*Command, 0)
|
matches := make([]*Command, 0)
|
||||||
for _, cmd := range c.commands {
|
for _, cmd := range c.commands {
|
||||||
if cmd.Name() == next || cmd.HasAlias(next) {
|
if commandNameMatches(cmd.Name(), next) || cmd.HasAlias(next) {
|
||||||
cmd.commandCalledAs.name = next
|
cmd.commandCalledAs.name = next
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@ -1328,7 +1328,7 @@ func (c *Command) Name() string {
|
|||||||
// HasAlias determines if a given string is an alias of the command.
|
// HasAlias determines if a given string is an alias of the command.
|
||||||
func (c *Command) HasAlias(s string) bool {
|
func (c *Command) HasAlias(s string) bool {
|
||||||
for _, a := range c.Aliases {
|
for _, a := range c.Aliases {
|
||||||
if a == s {
|
if commandNameMatches(a, s) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1695,3 +1695,14 @@ func (c *Command) updateParentsPflags() {
|
|||||||
c.parentsPflags.AddFlagSet(parent.PersistentFlags())
|
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)
|
t.Errorf("aCmdArgs expected: %q, got: %q", onetwo, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
EnablePrefixMatching = false
|
EnablePrefixMatching = defaultPrefixMatching
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAliasPrefixMatching(t *testing.T) {
|
func TestAliasPrefixMatching(t *testing.T) {
|
||||||
@ -349,7 +349,7 @@ func TestAliasPrefixMatching(t *testing.T) {
|
|||||||
t.Errorf("timesCmdArgs expected: %v, got: %v", onetwo, got)
|
t.Errorf("timesCmdArgs expected: %v, got: %v", onetwo, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
EnablePrefixMatching = false
|
EnablePrefixMatching = defaultPrefixMatching
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestChildSameName checks the correct behaviour of cobra in cases,
|
// 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) {
|
func TestRemoveCommand(t *testing.T) {
|
||||||
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
||||||
childCmd := &Command{Use: "child", 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) {
|
func TestEnableCommandSortingIsDisabled(t *testing.T) {
|
||||||
@ -1643,7 +1750,7 @@ func TestEnableCommandSortingIsDisabled(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EnableCommandSorting = true
|
EnableCommandSorting = defaultCommandSorting
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetOutput(t *testing.T) {
|
func TestSetOutput(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user