Replace prefix matching with aliases

This commit is contained in:
Sam Ghods 2014-09-25 16:03:34 -07:00 committed by spf13
parent 83b58a2f9b
commit 881657297e
2 changed files with 34 additions and 40 deletions

View File

@ -27,6 +27,7 @@ var cmdPrint = &Command{
var cmdEcho = &Command{ var cmdEcho = &Command{
Use: "echo [string to echo]", Use: "echo [string to echo]",
Aliases: []string{"say"},
Short: "Echo anything to the screen", Short: "Echo anything to the screen",
Long: `an utterly useless command for testing.`, Long: `an utterly useless command for testing.`,
Run: func(cmd *Command, args []string) { Run: func(cmd *Command, args []string) {
@ -38,7 +39,9 @@ var cmdTimes = &Command{
Use: "times [# times] [string to echo]", Use: "times [# times] [string to echo]",
Short: "Echo anything to the screen more times", Short: "Echo anything to the screen more times",
Long: `an slightly useless command for testing.`, Long: `an slightly useless command for testing.`,
Run: timesRunner, Run: func(cmd *Command, args []string) {
tt = args
},
} }
var cmdRootNoRun = &Command{ var cmdRootNoRun = &Command{
@ -62,10 +65,6 @@ var cmdRootWithRun = &Command{
}, },
} }
func timesRunner(cmd *Command, args []string) {
tt = args
}
func flagInit() { func flagInit() {
cmdEcho.ResetFlags() cmdEcho.ResetFlags()
cmdPrint.ResetFlags() cmdPrint.ResetFlags()
@ -195,8 +194,8 @@ func TestChildCommand(t *testing.T) {
} }
} }
func TestChildCommandPrefix(t *testing.T) { func TestCommandAlias(t *testing.T) {
noRRSetupTest("ech tim one two") noRRSetupTest("say times one two")
if te != nil || tp != nil { if te != nil || tp != nil {
t.Error("Wrong command called") t.Error("Wrong command called")
@ -226,23 +225,6 @@ func TestChildSameName(t *testing.T) {
} }
} }
func TestChildSameNamePrefix(t *testing.T) {
c := initializeWithSameName()
c.AddCommand(cmdPrint, cmdEcho)
c.SetArgs(strings.Split("pr one two", " "))
c.Execute()
if te != nil || tt != nil {
t.Error("Wrong command called")
}
if tp == nil {
t.Error("Wrong command called")
}
if strings.Join(tp, " ") != "one two" {
t.Error("Command didn't parse correctly")
}
}
func TestFlagLong(t *testing.T) { func TestFlagLong(t *testing.T) {
noRRSetupTest("echo --intone=13 something here") noRRSetupTest("echo --intone=13 something here")

View File

@ -35,6 +35,8 @@ type Command struct {
name string name string
// The one-line usage message. // The one-line usage message.
Use string Use string
// An array of aliases that can be used instead of the first word in Use.
Aliases []string
// The short description shown in the 'help' output. // The short description shown in the 'help' output.
Short string Short string
// The long message shown in the 'help <this-command>' output. // The long message shown in the 'help <this-command>' output.
@ -189,7 +191,10 @@ func (c *Command) UsageTemplate() string {
return `{{ $cmd := . }} return `{{ $cmd := . }}
Usage: {{if .Runnable}} Usage: {{if .Runnable}}
{{.UseLine}}{{if .HasFlags}} [flags]{{end}}{{end}}{{if .HasSubCommands}} {{.UseLine}}{{if .HasFlags}} [flags]{{end}}{{end}}{{if .HasSubCommands}}
{{ .CommandPath}} [command]{{end}} {{ .CommandPath}} [command]{{end}}{{if gt .Aliases 0}}
Aliases:
{{.NameAndAliases}}{{end}}
{{ if .HasSubCommands}} {{ if .HasSubCommands}}
Available Commands: {{range .Commands}}{{if .Runnable}} Available Commands: {{range .Commands}}{{if .Runnable}}
{{rpad .Use .UsagePadding }} {{.Short}}{{end}}{{end}} {{rpad .Use .UsagePadding }} {{.Short}}{{end}}{{end}}
@ -281,19 +286,11 @@ func (c *Command) Find(arrs []string) (*Command, []string, error) {
if len(args) > 0 && c.HasSubCommands() { if len(args) > 0 && c.HasSubCommands() {
argsWOflags := stripFlags(args) argsWOflags := stripFlags(args)
if len(argsWOflags) > 0 { if len(argsWOflags) > 0 {
matches := make([]*Command, 0)
for _, cmd := range c.commands { for _, cmd := range c.commands {
if cmd.Name() == argsWOflags[0] { // exact name match if cmd.Name() == argsWOflags[0] || cmd.HasAlias(argsWOflags[0]) { // exact name or alias match
return innerfind(cmd, argsMinusX(args, cmd.Name())) return innerfind(cmd, argsMinusX(args, argsWOflags[0]))
} else if strings.HasPrefix(cmd.Name(), argsWOflags[0]) { // prefix match
matches = append(matches, cmd)
} }
} }
// only accept a single prefix match - multiple matches would be ambiguous
if len(matches) == 1 {
return innerfind(matches[0], argsMinusX(args, argsWOflags[0]))
}
} }
} }
@ -302,8 +299,9 @@ func (c *Command) Find(arrs []string) (*Command, []string, error) {
commandFound, a := innerfind(c, arrs) commandFound, a := innerfind(c, arrs)
// if commander returned and not appropriately matched return nil & error // if commander returned and the first argument (if it exists) doesn't
if commandFound.Name() == c.Name() && !strings.HasPrefix(commandFound.Name(), arrs[0]) { // match the command name, return nil & error
if commandFound.Name() == c.Name() && len(arrs[0]) > 0 && commandFound.Name() != arrs[0] {
return nil, a, fmt.Errorf("unknown command %q\nRun 'help' for usage.\n", a[0]) return nil, a, fmt.Errorf("unknown command %q\nRun 'help' for usage.\n", a[0])
} }
@ -614,6 +612,20 @@ func (c *Command) Name() string {
return name return name
} }
// Determine 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 {
return true
}
}
return false
}
func (c *Command) NameAndAliases() string {
return strings.Join(append([]string{c.Name()}, c.Aliases...), ", ")
}
// Determine if the command is itself runnable // Determine if the command is itself runnable
func (c *Command) Runnable() bool { func (c *Command) Runnable() bool {
return c.Run != nil return c.Run != nil