diff --git a/README.md b/README.md index 5e93958..b2b9a66 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,13 @@ all commands. Cobra has an exceptionally clean interface and simple design without needless constructors or initialization methods. +Applications built with Cobra commands are designed to be as user friendly as +possible. Flags can be placed before or after the command (as long as a +confusing space isn’t provided). Both short and long flags can be used. A +command need not even be fully typed. The shortest unambiguous string will +suffice. Help is automatically generated and available for the application or +for a specific command using either the help command or the --help flag. + ## Concepts Cobra is built on a structure of commands & flags. @@ -283,14 +290,13 @@ You can provide your own command, function or template through the following met The latter two will also apply to any children commands. - ## Usage When the user provides an invalid flag or invalid command Cobra responds by showing the user the 'usage' ### Example -You may recognize this from the help above. That's because the default help +You may recognize this from the help above. That's because the default help embeds the usage as part of it's output. Usage: @@ -332,7 +338,22 @@ Like help the function and template are over ridable through public methods. command.SetUsageTemplate(s string) +## Debugging + +Cobra provides a ‘DebugFlags’ method on a command which when called will print +out everything Cobra knows about the flags for each command + +### Example + + command.DebugFlags() + ## Release Notes +* **0.9.0** June 17, 2014 + * flags can appears anywhere in the args (provided they are unambiguous) + * --help prints usage screen for app or command + * Prefix matching for commands + * Cleaner looking help and usage output + * Extensive test suite * **0.8.0** Nov 5, 2013 * Reworked interface to remove commander completely * Command now primary structure diff --git a/cobra_test.go b/cobra_test.go index 3f50dff..5f4af27 100644 --- a/cobra_test.go +++ b/cobra_test.go @@ -168,10 +168,7 @@ func checkOutputContains(t *testing.T, c *Command, check string) { } func TestSingleCommand(t *testing.T) { - c := initialize() - c.AddCommand(cmdPrint, cmdEcho) - c.SetArgs(strings.Split("print one two", " ")) - c.Execute() + noRRSetupTest("print one two") if te != nil || tt != nil { t.Error("Wrong command called") @@ -185,11 +182,7 @@ func TestSingleCommand(t *testing.T) { } func TestChildCommand(t *testing.T) { - c := initialize() - cmdEcho.AddCommand(cmdTimes) - c.AddCommand(cmdPrint, cmdEcho) - c.SetArgs(strings.Split("echo times one two", " ")) - c.Execute() + noRRSetupTest("echo times one two") if te != nil || tp != nil { t.Error("Wrong command called") @@ -203,11 +196,7 @@ func TestChildCommand(t *testing.T) { } func TestChildCommandPrefix(t *testing.T) { - c := initialize() - cmdEcho.AddCommand(cmdTimes) - c.AddCommand(cmdPrint, cmdEcho) - c.SetArgs(strings.Split("ech tim one two", " ")) - c.Execute() + noRRSetupTest("ech tim one two") if te != nil || tp != nil { t.Error("Wrong command called") @@ -255,10 +244,7 @@ func TestChildSameNamePrefix(t *testing.T) { } func TestFlagLong(t *testing.T) { - c := initialize() - c.AddCommand(cmdPrint, cmdEcho, cmdTimes) - c.SetArgs(strings.Split("echo --intone=13 something here", " ")) - c.Execute() + noRRSetupTest("echo --intone=13 something here") if strings.Join(te, " ") != "something here" { t.Errorf("flags didn't leave proper args remaining..%s given", te) @@ -272,10 +258,7 @@ func TestFlagLong(t *testing.T) { } func TestFlagShort(t *testing.T) { - c := initialize() - c.AddCommand(cmdPrint, cmdEcho, cmdTimes) - c.SetArgs(strings.Split("echo -i13 something here", " ")) - c.Execute() + noRRSetupTest("echo -i13 something here") if strings.Join(te, " ") != "something here" { t.Errorf("flags didn't leave proper args remaining..%s given", te) @@ -287,10 +270,7 @@ func TestFlagShort(t *testing.T) { t.Errorf("default flag value changed, 234 expected, %d given", flagi2) } - c = initialize() - c.AddCommand(cmdPrint, cmdEcho, cmdTimes) - c.SetArgs(strings.Split("echo -i 13 something here", " ")) - c.Execute() + noRRSetupTest("echo -i 13 something here") if strings.Join(te, " ") != "something here" { t.Errorf("flags didn't leave proper args remaining..%s given", te) @@ -302,11 +282,7 @@ func TestFlagShort(t *testing.T) { t.Errorf("default flag value changed, 234 expected, %d given", flagi2) } - // Testing same shortcode, different command - c = initialize() - c.AddCommand(cmdPrint, cmdEcho, cmdTimes) - c.SetArgs(strings.Split("print -i99 one two", " ")) - c.Execute() + noRRSetupTest("print -i99 one two") if strings.Join(tp, " ") != "one two" { t.Errorf("flags didn't leave proper args remaining..%s given", tp) @@ -320,33 +296,21 @@ func TestFlagShort(t *testing.T) { } func TestChildCommandFlags(t *testing.T) { - c := initialize() - cmdEcho.AddCommand(cmdTimes) - c.AddCommand(cmdPrint, cmdEcho) - c.SetArgs(strings.Split("echo times -j 99 one two", " ")) - c.Execute() + noRRSetupTest("echo times -j 99 one two") if strings.Join(tt, " ") != "one two" { t.Errorf("flags didn't leave proper args remaining..%s given", tt) } - buf := new(bytes.Buffer) // Testing with flag that shouldn't be persistent - c = initialize() - c.SetOutput(buf) - // define children - c.AddCommand(cmdPrint, cmdEcho) - // define grandchild - cmdEcho.AddCommand(cmdTimes) - c.SetArgs(strings.Split("echo times -j 99 -i77 one two", " ")) - e := c.Execute() + r := noRRSetupTest("echo times -j 99 -i77 one two") - if e == nil { + if r.Error == nil { t.Errorf("invalid flag should generate error") } - if !strings.Contains(buf.String(), "unknown shorthand") { - t.Errorf("Wrong error message displayed, \n %s", buf.String()) + if !strings.Contains(r.Output, "unknown shorthand") { + t.Errorf("Wrong error message displayed, \n %s", r.Output) } if flagi2 != 99 { @@ -358,61 +322,38 @@ func TestChildCommandFlags(t *testing.T) { } // Testing with flag only existing on child - buf.Reset() - c = initialize() - c.SetOutput(buf) - cmdEcho.AddCommand(cmdTimes) - c.AddCommand(cmdPrint, cmdEcho) - c.SetArgs(strings.Split("echo -j 99 -i77 one two", " ")) - err := c.Execute() + r = noRRSetupTest("echo -j 99 -i77 one two") - if err == nil { + if r.Error == nil { t.Errorf("invalid flag should generate error") } - if !strings.Contains(buf.String(), "intone=123") { - t.Errorf("Wrong error message displayed, \n %s", buf.String()) + if !strings.Contains(r.Output, "intone=123") { + t.Errorf("Wrong error message displayed, \n %s", r.Output) } // Testing flag with invalid input - buf.Reset() - c = initialize() - c.SetOutput(buf) - cmdEcho.AddCommand(cmdTimes) - c.AddCommand(cmdPrint, cmdEcho) - c.SetArgs(strings.Split("echo -i10E", " ")) - err = c.Execute() + r = noRRSetupTest("echo -i10E") - if err == nil { + if r.Error == nil { t.Errorf("invalid input should generate error") } - if !strings.Contains(buf.String(), "invalid argument \"10E\" for -i10E") { - t.Errorf("Wrong error message displayed, \n %s", buf.String()) + if !strings.Contains(r.Output, "invalid argument \"10E\" for -i10E") { + t.Errorf("Wrong error message displayed, \n %s", r.Output) } - } func TestTrailingCommandFlags(t *testing.T) { - buf := new(bytes.Buffer) - c := initialize() - c.SetOutput(buf) - cmdEcho.AddCommand(cmdTimes) - c.AddCommand(cmdPrint, cmdEcho) - c.SetArgs(strings.Split("echo two -x", " ")) - e3 := c.Execute() + x := fullSetupTest("echo two -x") - if e3 == nil { + if x.Error == nil { t.Errorf("invalid flag should generate error") } } func TestPersistentFlags(t *testing.T) { - c := initialize() - cmdEcho.AddCommand(cmdTimes) - c.AddCommand(cmdPrint, cmdEcho) - c.SetArgs(strings.Split("echo -s something more here", " ")) - c.Execute() + fullSetupTest("echo -s something more here") // persistentFlag should act like normal flag on it's own command if strings.Join(te, " ") != "more here" { @@ -424,11 +365,7 @@ func TestPersistentFlags(t *testing.T) { t.Errorf("string flag didn't get correct value, had %v", flags1) } - c = initialize() - cmdEcho.AddCommand(cmdTimes) - c.AddCommand(cmdPrint, cmdEcho) - c.SetArgs(strings.Split("echo times -s again -c test here", " ")) - c.Execute() + fullSetupTest("echo times -s again -c test here") if strings.Join(tt, " ") != "test here" { t.Errorf("flags didn't leave proper args remaining..%s given", tt) @@ -444,26 +381,15 @@ func TestPersistentFlags(t *testing.T) { } func TestHelpCommand(t *testing.T) { - c := initialize() - cmdEcho.AddCommand(cmdTimes) - c.AddCommand(cmdPrint, cmdEcho) - c.SetArgs(strings.Split("help echo", " ")) + c := fullSetupTest("help echo") + checkResultContains(t, c, cmdEcho.Long) - checkOutputContains(t, c, cmdEcho.Long) - - c = initialize() - cmdEcho.AddCommand(cmdTimes) - c.AddCommand(cmdPrint, cmdEcho) - c.SetArgs(strings.Split("help echo times", " ")) - - checkOutputContains(t, c, cmdTimes.Long) + r := fullSetupTest("help echo times") + checkResultContains(t, r, cmdTimes.Long) } func TestRunnableRootCommand(t *testing.T) { - c := initializeWithRootCmd() - c.AddCommand(cmdPrint, cmdEcho) - c.SetArgs([]string(nil)) - c.Execute() + fullSetupTest("") if rootcalled != true { t.Errorf("Root Function was not called") @@ -471,10 +397,7 @@ func TestRunnableRootCommand(t *testing.T) { } func TestRootFlags(t *testing.T) { - c := initializeWithRootCmd() - c.AddCommand(cmdPrint, cmdEcho) - c.SetArgs(strings.Split("-i 17 -b", " ")) - c.Execute() + fullSetupTest("-i 17 -b") if flagbr != true { t.Errorf("flag value should be true, %v given", flagbr) @@ -504,5 +427,56 @@ func TestRootHelp(t *testing.T) { } - checkOutputContains(t, c, "Available Commands:") +func TestFlagsBeforeCommand(t *testing.T) { + // short without space + x := fullSetupTest("-i10 echo") + if x.Error != nil { + t.Errorf("Valid Input shouldn't have errors, got:\n %q", x.Error) + } + + // short (int) with equals + // It appears that pflags doesn't support this... + // Commenting out until support can be added + + //x = noRRSetupTest("echo -i=10") + //if x.Error != nil { + //t.Errorf("Valid Input shouldn't have errors, got:\n %s", x.Error) + //} + + // long with equals + x = noRRSetupTest("--intone=123 echo one two") + if x.Error != nil { + t.Errorf("Valid Input shouldn't have errors, got:\n %s", x.Error) + } + + // With parsing error properly reported + x = fullSetupTest("-i10E echo") + if !strings.Contains(x.Output, "invalid argument \"10E\" for -i10E") { + t.Errorf("Wrong error message displayed, \n %s", x.Output) + } + + //With quotes + x = fullSetupTest("-s=\"walking\" echo") + if x.Error != nil { + t.Errorf("Valid Input shouldn't have errors, got:\n %q", x.Error) + } + + //With quotes and space + x = fullSetupTest("-s=\"walking fast\" echo") + if x.Error != nil { + t.Errorf("Valid Input shouldn't have errors, got:\n %q", x.Error) + } + + //With inner quote + x = fullSetupTest("-s=\"walking \\\"Inner Quote\\\" fast\" echo") + if x.Error != nil { + t.Errorf("Valid Input shouldn't have errors, got:\n %q", x.Error) + } + + //With quotes and space + x = fullSetupTest("-s=\"walking \\\"Inner Quote\\\" fast\" echo") + if x.Error != nil { + t.Errorf("Valid Input shouldn't have errors, got:\n %q", x.Error) + } + } diff --git a/command.go b/command.go index 538b6b7..c82e8eb 100644 --- a/command.go +++ b/command.go @@ -225,6 +225,45 @@ func (c *Command) resetChildrensParents() { } } +func stripFlags(args []string) []string { + if len(args) < 1 { + return args + } + + commands := []string{} + + inQuote := false + for _, y := range args { + if !inQuote { + switch { + case strings.HasPrefix(y, "\""): + inQuote = true + case strings.Contains(y, "=\""): + inQuote = true + case !strings.HasPrefix(y, "-"): + commands = append(commands, y) + } + } + + if strings.HasSuffix(y, "\"") && !strings.HasSuffix(y, "\\\"") { + inQuote = false + } + } + + return commands +} + +func argsMinusX(args []string, x string) []string { + newargs := []string{} + + for _, y := range args { + if x != y { + newargs = append(newargs, y) + } + } + return newargs +} + // 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(arrs []string) (*Command, []string, error) { @@ -240,18 +279,21 @@ func (c *Command) Find(arrs []string) (*Command, []string, error) { innerfind = func(c *Command, args []string) (*Command, []string) { if len(args) > 0 && c.HasSubCommands() { - matches := make([]*Command, 0) - for _, cmd := range c.commands { - if cmd.Name() == args[0] { // exact name match - return innerfind(cmd, args[1:]) - } else if strings.HasPrefix(cmd.Name(), args[0]) { // prefix match - matches = append(matches, cmd) + argsWOflags := stripFlags(args) + if len(argsWOflags) > 0 { + matches := make([]*Command, 0) + for _, cmd := range c.commands { + if cmd.Name() == argsWOflags[0] { // exact name match + return innerfind(cmd, argsMinusX(args, cmd.Name())) + } 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], args[1:]) + // only accept a single prefix match - multiple matches would be ambiguous + if len(matches) == 1 { + return innerfind(matches[0], argsMinusX(args, argsWOflags[0])) + } } } @@ -314,6 +356,18 @@ func (c *Command) execute(a []string) (err error) { } } +func (c *Command) errorMsgFromParse() string { + s := c.flagErrorBuf.String() + + x := strings.Split(s, "\n") + + if len(x) > 0 { + return x[0] + } else { + return "" + } +} + // Call execute to use the args (os.Args[1:] by default) // and run through the command tree finding appropriate matches // for commands and then corresponding flags. @@ -352,7 +406,8 @@ func (c *Command) Execute() (err error) { e := c.ParseFlags(args) if e != nil { // Flags parsing had an error. - fmt.Println(e) + //fmt.Println(e) + c.Println(c.errorMsgFromParse()) c.Usage() return e } else {