Add decent usage message

This commit is contained in:
spf13 2013-09-12 10:32:51 -04:00
parent 6067837866
commit 8858462331
2 changed files with 140 additions and 21 deletions

157
cobra.go
View file

@ -22,7 +22,10 @@ import (
flag "github.com/spf13/pflag" flag "github.com/spf13/pflag"
"io" "io"
"os" "os"
"reflect"
"strconv"
"strings" "strings"
"text/template"
) )
var _ = flag.ContinueOnError var _ = flag.ContinueOnError
@ -32,14 +35,19 @@ type Commander struct {
// A Commander is also a Command for top level and global help & flags // A Commander is also a Command for top level and global help & flags
Command Command
args []string args []string
output io.Writer // nil means stderr; use out() accessor 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
} }
// Provide the user with a new commander. // Provide the user with a new commander.
func NewCommander() (c *Commander) { func NewCommander() (c *Commander) {
c = new(Commander) c = new(Commander)
c.cmdr = c c.cmdr = c
c.UsageFunc = c.defaultUsage
c.initTemplates()
return return
} }
@ -73,6 +81,14 @@ func (c *Commander) out() io.Writer {
return c.output return c.output
} }
func (cmdr *Commander) defaultUsage(c *Command) error {
err := tmpl(cmdr.out(), cmdr.UsageTemplate, c)
if err != nil {
c.Println(err)
}
return err
}
//Print to out //Print to out
func (c *Commander) POut(i ...interface{}) { func (c *Commander) POut(i ...interface{}) {
fmt.Fprint(c.out(), i...) fmt.Fprint(c.out(), i...)
@ -84,6 +100,29 @@ func (c *Commander) SetOutput(output io.Writer) {
c.output = output c.output = output
} }
func (c *Commander) initTemplates() {
c.UsageTemplate = `{{ $cmd := . }}{{.CommandPath | printf "%-11s"}} :: {{.Short}}
Usage:
{{.UseLine}}{{if .HasSubCommands}} command{{end}}{{if .HasFlags}} [flags]{{end}}
{{ if .HasSubCommands}}
The commands are:
{{range .Commands}}{{if .Runnable}}
{{.UseLine | printf "%-11s"}} {{.Short}}{{end}}{{end}}
Use "{{$.CommandPath}} help [command]" for more information about a command.
{{end}}
Additional help topics: {{if gt .Commands 0 }}
{{range .Commands}}{{if not .Runnable}}{{.CommandPath | printf "%-11s"}} {{.Short}}{{end}}{{end}}{{end}}{{if gt .Parent.Commands 1 }}
{{range .Parent.Commands}}{{if .Runnable}}{{if not (eq .Name $cmd.Name) }}{{end}}
{{.CommandPath | printf "%-11s"}} :: {{.Short}}{{end}}{{end}}{{end}}
Use "{{.Commander.Name}} help [topic]" for more information about that topic.
`
c.HelpTemplate = `{{if .Runnable}}Usage: {{.ProgramName}} {{.UsageLine}}
{{end}}{{.Long | trim}}
`
}
// Command is just that, a command for your application. // Command is just that, a command for your application.
// eg. 'go run' ... 'run' is the command. Cobra requires // eg. 'go run' ... 'run' is the command. Cobra requires
// you to define the usage and description as part of your command // you to define the usage and description as part of your command
@ -135,6 +174,10 @@ func (c *Command) Find(args []string) (cmd *Command, a []string, err error) {
return nil, nil, nil return nil, nil, nil
} }
func (c *Command) Commander() *Commander {
return c.cmdr
}
// execute the command determined by args and the command tree // execute the command determined by args and the command tree
func (c *Command) execute(args []string) (err error) { func (c *Command) execute(args []string) (err error) {
err = fmt.Errorf("unknown subcommand %q\nRun 'help' for usage.\n", args[0]) err = fmt.Errorf("unknown subcommand %q\nRun 'help' for usage.\n", args[0])
@ -147,6 +190,7 @@ func (c *Command) execute(args []string) (err error) {
if e == nil { if e == nil {
err = cmd.ParseFlags(a) err = cmd.ParseFlags(a)
if err != nil { if err != nil {
cmd.Usage()
return err return err
} else { } else {
argWoFlags := cmd.Flags().Args() argWoFlags := cmd.Flags().Args()
@ -163,6 +207,10 @@ func (c *Command) ResetCommands() {
c.commands = nil c.commands = nil
} }
func (c *Command) Commands() []*Command {
return c.commands
}
// Add one or many commands as children of this // Add one or many commands as children of this
func (c *Command) AddCommand(cmds ...*Command) { func (c *Command) AddCommand(cmds ...*Command) {
for i, x := range cmds { for i, x := range cmds {
@ -189,20 +237,33 @@ func (c *Command) Printf(format string, i ...interface{}) {
c.Print(str) c.Print(str)
} }
// The full usage for a given command (including parents) func (c *Command) Usage() error {
func (c *Command) Usage(depth ...int) string { err := c.cmdr.UsageFunc(c)
i := 0 if err != nil {
if len(depth) > 0 { fmt.Println(err)
i = depth[0]
} }
if c.HasParent() { return err
return c.parent.Usage(i+1) + " " + c.Use }
} else if i > 0 {
return c.Name() func (c *Command) CommandPath() string {
} else { str := c.Name()
return c.Use x := c
for x.HasParent() {
str = x.parent.Name() + " " + str
x = x.parent
} }
return str
}
//The full usage for a given command (including parents)
func (c *Command) UseLine() string {
str := ""
if c.HasParent() {
str = c.parent.CommandPath() + " "
}
return str + c.Use
} }
// For use in determining which flags have been assigned to which commands // For use in determining which flags have been assigned to which commands
@ -251,13 +312,13 @@ func (c *Command) DebugFlags() {
} }
// Usage prints the usage details to the standard output. // Usage prints the usage details to the standard output.
func (c *Command) PrintUsage() { //func (c *Command) PrintUsage() {
if c.Runnable() { //if c.Runnable() {
c.Printf("usage: %s\n\n", c.Usage()) //c.Printf("usage: %s\n\n", c.Usage())
} //}
c.Println(strings.Trim(c.Long, "\n")) //c.Println(strings.Trim(c.Long, "\n"))
} //}
// Name returns the command's name: the first word in the use line. // Name returns the command's name: the first word in the use line.
func (c *Command) Name() string { func (c *Command) Name() string {
@ -382,3 +443,61 @@ func (c *Command) mergePersistentFlags() {
rmerge(c) rmerge(c)
} }
func (c *Command) Parent() *Command {
return c.parent
}
func Gt(a interface{}, b interface{}) bool {
var left, right int64
av := reflect.ValueOf(a)
switch av.Kind() {
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
left = int64(av.Len())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
left = av.Int()
case reflect.String:
left, _ = strconv.ParseInt(av.String(), 10, 64)
}
bv := reflect.ValueOf(b)
switch bv.Kind() {
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
right = int64(bv.Len())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
right = bv.Int()
case reflect.String:
right, _ = strconv.ParseInt(bv.String(), 10, 64)
}
return left > right
}
func Eq(a interface{}, b interface{}) bool {
av := reflect.ValueOf(a)
bv := reflect.ValueOf(b)
switch av.Kind() {
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
panic("Eq called on unsupported type")
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return av.Int() == bv.Int()
case reflect.String:
return av.String() == bv.String()
}
return false
}
// tmpl executes the given template text on data, writing the result to w.
func tmpl(w io.Writer, text string, data interface{}) error {
t := template.New("top")
t.Funcs(template.FuncMap{
"trim": strings.TrimSpace,
"gt": Gt,
"eq": Eq,
})
template.Must(t.Parse(text))
return t.Execute(w, data)
}

View file

@ -35,7 +35,7 @@ var cmdEcho = &Command{
} }
var cmdTimes = &Command{ var cmdTimes = &Command{
Use: "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: timesRunner,
@ -69,7 +69,7 @@ func commandInit() {
func initialize() *Commander { func initialize() *Commander {
tt, tp, te = nil, nil, nil tt, tp, te = nil, nil, nil
var c = NewCommander() var c = NewCommander()
c.SetName("cobra test") c.SetName("cobratest")
flagInit() flagInit()
commandInit() commandInit()
return c return c