mirror of
https://github.com/spf13/cobra
synced 2025-05-06 05:17:21 +00:00
Traverse commands for args on parents.
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
This commit is contained in:
parent
bc69223348
commit
6259b8dea9
2 changed files with 111 additions and 26 deletions
114
command.go
114
command.go
|
@ -125,6 +125,9 @@ type Command struct {
|
||||||
// Must be > 0.
|
// Must be > 0.
|
||||||
SuggestionsMinimumDistance int
|
SuggestionsMinimumDistance int
|
||||||
|
|
||||||
|
// TraverseChildCommands parses flags on all parents before the child command
|
||||||
|
TraverseChildCommands bool
|
||||||
|
|
||||||
// name is the command name, usually the executable's name.
|
// name is the command name, usually the executable's name.
|
||||||
name string
|
name string
|
||||||
// commands is the list of commands supported by this program.
|
// commands is the list of commands supported by this program.
|
||||||
|
@ -475,13 +478,14 @@ func argsMinusFirstX(args []string, x string) []string {
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isFlagArg(arg string) bool {
|
||||||
|
return ((len(arg) >= 3 && arg[0] == '-') ||
|
||||||
|
(len(arg) >= 2 && arg[1] == '-' && arg[1] != '-'))
|
||||||
|
}
|
||||||
|
|
||||||
// Find the target command given the args and command tree
|
// Find the target command given the args and command tree
|
||||||
// Meant to be run on the highest node. Only searches down.
|
// Meant to be run on the highest node. Only searches down.
|
||||||
func (c *Command) Find(args []string) (*Command, []string, error) {
|
func (c *Command) Find(args []string) (*Command, []string, error) {
|
||||||
if c == nil {
|
|
||||||
return nil, nil, fmt.Errorf("Called find() on a nil Command")
|
|
||||||
}
|
|
||||||
|
|
||||||
var innerfind func(*Command, []string) (*Command, []string)
|
var innerfind func(*Command, []string) (*Command, []string)
|
||||||
|
|
||||||
innerfind = func(c *Command, innerArgs []string) (*Command, []string) {
|
innerfind = func(c *Command, innerArgs []string) (*Command, []string) {
|
||||||
|
@ -490,28 +494,11 @@ func (c *Command) Find(args []string) (*Command, []string, error) {
|
||||||
return c, innerArgs
|
return c, innerArgs
|
||||||
}
|
}
|
||||||
nextSubCmd := argsWOflags[0]
|
nextSubCmd := argsWOflags[0]
|
||||||
matches := make([]*Command, 0)
|
|
||||||
for _, cmd := range c.commands {
|
cmd := c.findNext(nextSubCmd)
|
||||||
if cmd.Name() == nextSubCmd || cmd.HasAlias(nextSubCmd) { // exact name or alias match
|
if cmd != nil {
|
||||||
return innerfind(cmd, argsMinusFirstX(innerArgs, nextSubCmd))
|
return innerfind(cmd, argsMinusFirstX(innerArgs, nextSubCmd))
|
||||||
}
|
}
|
||||||
if EnablePrefixMatching {
|
|
||||||
if strings.HasPrefix(cmd.Name(), nextSubCmd) { // prefix match
|
|
||||||
matches = append(matches, cmd)
|
|
||||||
}
|
|
||||||
for _, x := range cmd.Aliases {
|
|
||||||
if strings.HasPrefix(x, nextSubCmd) {
|
|
||||||
matches = append(matches, cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// only accept a single prefix match - multiple matches would be ambiguous
|
|
||||||
if len(matches) == 1 {
|
|
||||||
return innerfind(matches[0], argsMinusFirstX(innerArgs, argsWOflags[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, innerArgs
|
return c, innerArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -539,6 +526,62 @@ func (c *Command) findSuggestions(arg string) string {
|
||||||
return suggestionsString
|
return suggestionsString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Command) findNext(next string) *Command {
|
||||||
|
matches := make([]*Command, 0)
|
||||||
|
for _, cmd := range c.commands {
|
||||||
|
if cmd.Name() == next || cmd.HasAlias(next) {
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
if EnablePrefixMatching && cmd.HasNameOrAliasPrefix(next) {
|
||||||
|
matches = append(matches, cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(matches) == 1 {
|
||||||
|
return matches[0]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverse the command tree to find the command, and parse args for
|
||||||
|
// each parent.
|
||||||
|
func (c *Command) Traverse(args []string) (*Command, []string, error) {
|
||||||
|
flags := []string{}
|
||||||
|
inFlag := false
|
||||||
|
|
||||||
|
for i, arg := range args {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(arg, "--") && !strings.Contains(arg, "="):
|
||||||
|
// TODO: this isn't quite right, we should really check ahead for 'true' or 'false'
|
||||||
|
inFlag = !hasNoOptDefVal(arg[2:], c.Flags())
|
||||||
|
flags = append(flags, arg)
|
||||||
|
continue
|
||||||
|
case strings.HasPrefix(arg, "-") && !strings.Contains(arg, "=") && len(arg) == 2 && !shortHasNoOptDefVal(arg[1:], c.Flags()):
|
||||||
|
inFlag = true
|
||||||
|
flags = append(flags, arg)
|
||||||
|
continue
|
||||||
|
case inFlag:
|
||||||
|
inFlag = false
|
||||||
|
flags = append(flags, arg)
|
||||||
|
continue
|
||||||
|
case isFlagArg(arg):
|
||||||
|
flags = append(flags, arg)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := c.findNext(arg)
|
||||||
|
if cmd == nil {
|
||||||
|
return c, args, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.ParseFlags(flags); err != nil {
|
||||||
|
return nil, args, err
|
||||||
|
}
|
||||||
|
return cmd.Traverse(args[i+1:])
|
||||||
|
}
|
||||||
|
return c, args, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SuggestionsFor provides suggestions for the typedName.
|
// SuggestionsFor provides suggestions for the typedName.
|
||||||
func (c *Command) SuggestionsFor(typedName string) []string {
|
func (c *Command) SuggestionsFor(typedName string) []string {
|
||||||
suggestions := []string{}
|
suggestions := []string{}
|
||||||
|
@ -714,7 +757,12 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
|
||||||
args = c.args
|
args = c.args
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd, flags, err := c.Find(args)
|
var flags []string
|
||||||
|
if c.TraverseChildCommands {
|
||||||
|
cmd, flags, err = c.Traverse(args)
|
||||||
|
} else {
|
||||||
|
cmd, flags, err = c.Find(args)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If found parse to a subcommand and then failed, talk about the subcommand
|
// If found parse to a subcommand and then failed, talk about the subcommand
|
||||||
if cmd != nil {
|
if cmd != nil {
|
||||||
|
@ -726,6 +774,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
|
||||||
}
|
}
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cmd.execute(flags)
|
err = cmd.execute(flags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Always show help if requested, even if SilenceErrors is in
|
// Always show help if requested, even if SilenceErrors is in
|
||||||
|
@ -993,7 +1042,20 @@ func (c *Command) HasAlias(s string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameAndAliases returns string containing name and all aliases
|
// HasNameOrAliasPrefix returns true if the Name or any of aliases start
|
||||||
|
// with prefix
|
||||||
|
func (c *Command) HasNameOrAliasPrefix(prefix string) bool {
|
||||||
|
if strings.HasPrefix(c.Name(), prefix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, alias := range c.Aliases {
|
||||||
|
if strings.HasPrefix(alias, prefix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Command) NameAndAliases() string {
|
func (c *Command) NameAndAliases() string {
|
||||||
return strings.Join(append([]string{c.Name()}, c.Aliases...), ", ")
|
return strings.Join(append([]string{c.Name()}, c.Aliases...), ", ")
|
||||||
}
|
}
|
||||||
|
|
|
@ -347,3 +347,26 @@ func TestSetHelpCommand(t *testing.T) {
|
||||||
t.Errorf("Expected to contain %q message, but got %q", correctMessage, output.String())
|
t.Errorf("Expected to contain %q message, but got %q", correctMessage, output.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunWithTraverse(t *testing.T) {
|
||||||
|
cmd := &Command{
|
||||||
|
Use: "do",
|
||||||
|
TraverseChildCommands: true,
|
||||||
|
}
|
||||||
|
cmd.Flags().String("foo", "", "foo things")
|
||||||
|
|
||||||
|
sub := &Command{Use: "next"}
|
||||||
|
sub.Flags().String("add", "", "add things")
|
||||||
|
cmd.AddCommand(sub)
|
||||||
|
|
||||||
|
c, args, err := cmd.Traverse([]string{"--foo", "ok", "next"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected no error: %s", err)
|
||||||
|
}
|
||||||
|
if len(args) != 0 {
|
||||||
|
t.Fatalf("wrong args %s", args)
|
||||||
|
}
|
||||||
|
if c.Name() != sub.Name() {
|
||||||
|
t.Fatalf("wrong command %q expected %q", c.Name(), sub.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue