Merge pull request #346 from codegangsta/category_sort_2

Add option to make categories with command, to display a more structured help
This commit is contained in:
Jesse Szwedko 2016-03-26 15:39:47 -07:00
commit bc465beccc
6 changed files with 158 additions and 8 deletions

View File

@ -329,6 +329,45 @@ app.Commands = []cli.Command{
...
```
### Subcommands categories
For additional organization in apps that have many subcommands, you can
associate a category for each command to group them together in the help
output.
E.g.
```go
...
app.Commands = []cli.Command{
{
Name: "noop",
},
{
Name: "add",
Category: "template",
},
{
Name: "remove",
Category: "template",
},
}
...
```
Will include:
```
...
COMMANDS:
noop
Template actions:
add
remove
...
```
### Bash Completion
You can enable completion commands by setting the `EnableBashCompletion`

14
app.go
View File

@ -6,6 +6,7 @@ import (
"io/ioutil"
"os"
"path"
"sort"
"time"
)
@ -34,6 +35,8 @@ type App struct {
HideHelp bool
// Boolean to hide built-in version flag and the VERSION section of help
HideVersion bool
// Populate on app startup, only gettable throught method Categories()
categories CommandCategories
// An action to execute when the bash-completion flag is set
BashComplete func(context *Context)
// An action to execute before any subcommands are run, but after the context is ready
@ -104,6 +107,12 @@ func (a *App) Run(arguments []string) (err error) {
}
a.Commands = newCmds
a.categories = CommandCategories{}
for _, command := range a.Commands {
a.categories = a.categories.AddCommand(command.Category, command)
}
sort.Sort(a.categories)
// append help to commands
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand)
@ -316,6 +325,11 @@ func (a *App) Command(name string) *Command {
return nil
}
// Returnes the array containing all the categories with the commands they contain
func (a *App) Categories() CommandCategories {
return a.categories
}
func (a *App) hasFlag(flag Flag) bool {
for _, f := range a.Flags {
if flag == f {

View File

@ -8,6 +8,7 @@ import (
"io"
"io/ioutil"
"os"
"reflect"
"strings"
"testing"
)
@ -939,6 +940,55 @@ func TestApp_Run_Version(t *testing.T) {
}
}
func TestApp_Run_Categories(t *testing.T) {
app := NewApp()
app.Name = "categories"
app.Commands = []Command{
Command{
Name: "command1",
Category: "1",
},
Command{
Name: "command2",
Category: "1",
},
Command{
Name: "command3",
Category: "2",
},
}
buf := new(bytes.Buffer)
app.Writer = buf
app.Run([]string{"categories"})
expect := CommandCategories{
&CommandCategory{
Name: "1",
Commands: []Command{
app.Commands[0],
app.Commands[1],
},
},
&CommandCategory{
Name: "2",
Commands: []Command{
app.Commands[2],
},
},
}
if !reflect.DeepEqual(app.Categories(), expect) {
t.Fatalf("expected categories %#v, to equal %#v", app.Categories(), expect)
}
output := buf.String()
t.Logf("output: %q\n", buf.Bytes())
if !strings.Contains(output, "1:\n command1") {
t.Errorf("want buffer to include category %q, did not: \n%q", "1:\n command1", output)
}
}
func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
app := NewApp()
app.Action = func(c *Context) {}

30
category.go Normal file
View File

@ -0,0 +1,30 @@
package cli
type CommandCategories []*CommandCategory
type CommandCategory struct {
Name string
Commands Commands
}
func (c CommandCategories) Less(i, j int) bool {
return c[i].Name < c[j].Name
}
func (c CommandCategories) Len() int {
return len(c)
}
func (c CommandCategories) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}
func (c CommandCategories) AddCommand(category string, command Command) CommandCategories {
for _, commandCategory := range c {
if commandCategory.Name == category {
commandCategory.Commands = append(commandCategory.Commands, command)
return c
}
}
return append(c, &CommandCategory{Name: category, Commands: []Command{command}})
}

View File

@ -3,6 +3,7 @@ package cli
import (
"fmt"
"io/ioutil"
"sort"
"strings"
)
@ -22,6 +23,8 @@ type Command struct {
Description string
// A short description of the arguments of this command
ArgsUsage string
// The category the command is part of
Category string
// The function to call when checking for bash command completions
BashComplete func(context *Context)
// An action to execute before any sub-subcommands are run, but after the context is ready
@ -37,7 +40,7 @@ type Command struct {
// If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted.
OnUsageError func(context *Context, err error) error
// List of child commands
Subcommands []Command
Subcommands Commands
// List of flags to parse
Flags []Flag
// Treat all flags as normal arguments if true
@ -59,6 +62,8 @@ func (c Command) FullName() string {
return strings.Join(c.commandNamePath, " ")
}
type Commands []Command
// Invokes the command given the context, parses ctx.Args() to generate command-specific flags
func (c Command) Run(ctx *Context) (err error) {
if len(c.Subcommands) > 0 {
@ -227,6 +232,13 @@ func (c Command) startApp(ctx *Context) error {
app.Email = ctx.App.Email
app.Writer = ctx.App.Writer
app.categories = CommandCategories{}
for _, command := range c.Subcommands {
app.categories = app.categories.AddCommand(command.Category, command)
}
sort.Sort(app.categories)
// bash completion
app.EnableBashCompletion = ctx.App.EnableBashCompletion
if c.BashComplete != nil {

15
help.go
View File

@ -23,8 +23,9 @@ VERSION:
AUTHOR(S):
{{range .Authors}}{{ . }}{{end}}
{{end}}{{if .Commands}}
COMMANDS:
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
COMMANDS:{{range .Categories}}{{if .Name}}
{{.Name}}{{ ":" }}{{end}}{{range .Commands}}
{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}}
{{end}}{{end}}{{if .Flags}}
GLOBAL OPTIONS:
{{range .Flags}}{{.}}
@ -41,7 +42,10 @@ var CommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.HelpName}}{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Description}}
{{.HelpName}}{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Category}}
CATEGORY:
{{.Category}}{{end}}{{if .Description}}
DESCRIPTION:
{{.Description}}{{end}}{{if .Flags}}
@ -60,8 +64,9 @@ var SubcommandHelpTemplate = `NAME:
USAGE:
{{.HelpName}} command{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
COMMANDS:
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
COMMANDS:{{range .Categories}}{{if .Name}}
{{.Name}}{{ ":" }}{{end}}{{range .Commands}}
{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}}
{{end}}{{if .Flags}}
OPTIONS:
{{range .Flags}}{{.}}