From fb2146e9e527b9ebcc19fa30e26aa294ab786578 Mon Sep 17 00:00:00 2001 From: spf13 Date: Tue, 24 Sep 2013 16:03:22 -0400 Subject: [PATCH] Add support for help command --- cobra_test.go | 33 ++++++++++++++++++++++++++++---- command.go | 22 ++++++++++++++------- commander.go | 53 ++++++++++++++++++++++++++++++++++++++++----------- 3 files changed, 86 insertions(+), 22 deletions(-) diff --git a/cobra_test.go b/cobra_test.go index 142768d9..50da6af4 100644 --- a/cobra_test.go +++ b/cobra_test.go @@ -215,9 +215,9 @@ func TestChildCommandFlags(t *testing.T) { } // Testing with flag only existing on child - buf2 := new(bytes.Buffer) + buf.Reset() c = initialize() - c.SetOutput(buf2) + c.SetOutput(buf) cmdEcho.AddCommand(cmdTimes) c.AddCommand(cmdPrint, cmdEcho) c.SetArgs(strings.Split("echo -j 99 -i77 one two", " ")) @@ -227,10 +227,9 @@ func TestChildCommandFlags(t *testing.T) { t.Errorf("invalid flag should generate error") } - if !strings.Contains(buf2.String(), "intone=123") { + if !strings.Contains(buf.String(), "intone=123") { t.Errorf("Wrong error message displayed, \n %s", buf.String()) } - } func TestPersistentFlags(t *testing.T) { @@ -268,3 +267,29 @@ func TestPersistentFlags(t *testing.T) { t.Errorf("local flag not parsed correctly. Expected false, had %v", flagb2) } } + +func TestHelpCommand(t *testing.T) { + buf := new(bytes.Buffer) + c := initialize() + cmdEcho.AddCommand(cmdTimes) + c.AddCommand(cmdPrint, cmdEcho) + c.SetArgs(strings.Split("help echo", " ")) + c.SetOutput(buf) + c.Execute() + + if !strings.Contains(buf.String(), cmdEcho.Long) { + t.Errorf("Wrong error message displayed, \n %s", buf.String()) + } + + buf.Reset() + c = initialize() + cmdEcho.AddCommand(cmdTimes) + c.AddCommand(cmdPrint, cmdEcho) + c.SetArgs(strings.Split("help echo times", " ")) + c.SetOutput(buf) + c.Execute() + + if !strings.Contains(buf.String(), cmdTimes.Long) { + t.Errorf("Wrong error message displayed, \n %s", buf.String()) + } +} diff --git a/command.go b/command.go index 457853d1..34f3120b 100644 --- a/command.go +++ b/command.go @@ -53,13 +53,14 @@ type Command struct { } // 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(args []string) (cmd *Command, a []string, err error) { if c == nil { return nil, nil, fmt.Errorf("Called find() on a nil Command") } validSubCommand := false - if len(args) > 1 && c.HasSubCommands() { + if len(args) > 0 && c.HasSubCommands() { for _, cmd := range c.commands { if cmd.Name() == args[0] { validSubCommand = true @@ -74,7 +75,7 @@ func (c *Command) Find(args []string) (cmd *Command, a []string, err error) { return nil, nil, nil } -func (c *Command) Commander() *Commander { +func (c *Command) Root() *Command { var findRoot func(*Command) *Command findRoot = func(x *Command) *Command { @@ -84,7 +85,12 @@ func (c *Command) Commander() *Commander { return x } } - cmdr := findRoot(c) + + return findRoot(c) +} + +func (c *Command) Commander() *Commander { + cmdr := c.Root() if cmdr.cmdr != nil { return cmdr.cmdr } else { @@ -108,7 +114,12 @@ func (c *Command) execute(args []string) (err error) { if e == nil { err = cmd.ParseFlags(a) if err != nil { - cmd.Usage() + // report flag parsing error + c.Println(strings.Split(err.Error(), "\n")[0]) + erx := cmd.Usage() + if erx != nil { + return erx + } return err } else { argWoFlags := cmd.Flags().Args() @@ -162,9 +173,6 @@ func (c *Command) Printf(format string, i ...interface{}) { // Can be defined by user by overriding Commander.UsageFunc func (c *Command) Usage() error { err := c.Commander().UsageFunc(c) - if err != nil { - fmt.Println(err) - } return err } diff --git a/commander.go b/commander.go index fb2fbf19..e1dae1b9 100644 --- a/commander.go +++ b/commander.go @@ -28,10 +28,12 @@ type Commander struct { Command args []string - output *io.Writer // nil means stderr; use out() accessor - UsageFunc func(*Command) error // Usage can be defined by application - UsageTemplate string // Can be defined by Application - HelpTemplate string // Can be defined by Application + output *io.Writer // nil means stderr; use out() accessor + UsageFunc func(*Command) error // Usage can be defined by application + UsageTemplate string // Can be defined by Application + HelpTemplate string // Can be defined by Application + HelpFunc func(*Command, []string) // Help can be defined by application + HelpCommand *Command } // Provide the user with a new commander. @@ -39,10 +41,24 @@ func NewCommander() (c *Commander) { c = new(Commander) c.cmdr = c c.UsageFunc = c.defaultUsage + c.HelpFunc = c.defaultHelp c.initTemplates() return } +func (c *Commander) initHelp() { + if c.HelpCommand == nil { + c.HelpCommand = &Command{ + Use: "help [command to learn about]", + Short: "Help about any command", + Long: `Help provides help for any command in the application. + Simply type ` + c.Name() + ` help [path to command] for full details.`, + Run: c.HelpFunc, + } + } + c.AddCommand(c.HelpCommand) +} + // Name for commander, should match application name func (c *Commander) SetName(name string) { c.name = name @@ -58,6 +74,9 @@ func (c *Commander) SetArgs(a []string) { // and run through the command tree finding appropriate matches // for commands and then corresponding flags. func (c *Commander) Execute() (err error) { + // initialize help as the last point possible to allow for user + // overriding + c.initHelp() if len(c.args) == 0 { err = c.execute(os.Args[1:]) } else { @@ -75,12 +94,25 @@ func (c *Commander) out() io.Writer { func (cmdr *Commander) defaultUsage(c *Command) error { err := tmpl(cmdr.out(), cmdr.UsageTemplate, c) - if err != nil { - c.Println(err) - } return err } +func (cmdr *Commander) defaultHelp(c *Command, args []string) { + cmd, _, e := c.Root().Find(args) + if cmd == nil { + cmdr.Printf("Unknown help topic %#q. Run '%v help'.\n", args, cmdr.Name()) + return + } + if e != nil { + cmdr.Printf("Unknown help topic %#q. Run '%v help'.\n", args, cmdr.Name()) + } else { + err := tmpl(cmdr.out(), cmdr.HelpTemplate, cmd) + if err != nil { + c.Println(err) + } + } +} + //Print to out func (c *Commander) PrintOut(i ...interface{}) { fmt.Fprint(c.out(), i...) @@ -90,7 +122,6 @@ func (c *Commander) PrintOut(i ...interface{}) { // If output is nil, os.Stderr is used. func (c *Commander) SetOutput(output io.Writer) { c.output = &output - //*c.output = output } func (c *Commander) initTemplates() { @@ -112,8 +143,8 @@ Additional help topics: {{if gt .Commands 0 }}{{range .Commands}}{{if not .Runna Use "{{.Commander.Name}} help [command]" for more information about that command. ` - c.HelpTemplate = `{{if .Runnable}}Usage: {{.ProgramName}} {{.UsageLine}} - -{{end}}{{.Long | trim}} + c.HelpTemplate = `{{.Name}} +{{.Long | trim}} +{{if .Runnable}}{{.Usage}}{{end}} ` }