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:
commit
bc465beccc
39
README.md
39
README.md
@ -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
|
### Bash Completion
|
||||||
|
|
||||||
You can enable completion commands by setting the `EnableBashCompletion`
|
You can enable completion commands by setting the `EnableBashCompletion`
|
||||||
|
14
app.go
14
app.go
@ -6,6 +6,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -34,6 +35,8 @@ type App struct {
|
|||||||
HideHelp bool
|
HideHelp bool
|
||||||
// Boolean to hide built-in version flag and the VERSION section of help
|
// Boolean to hide built-in version flag and the VERSION section of help
|
||||||
HideVersion bool
|
HideVersion bool
|
||||||
|
// Populate on app startup, only gettable throught method Categories()
|
||||||
|
categories CommandCategories
|
||||||
// An action to execute when the bash-completion flag is set
|
// An action to execute when the bash-completion flag is set
|
||||||
BashComplete func(context *Context)
|
BashComplete func(context *Context)
|
||||||
// An action to execute before any subcommands are run, but after the context is ready
|
// 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.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
|
// append help to commands
|
||||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
||||||
a.Commands = append(a.Commands, helpCommand)
|
a.Commands = append(a.Commands, helpCommand)
|
||||||
@ -316,6 +325,11 @@ func (a *App) Command(name string) *Command {
|
|||||||
return nil
|
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 {
|
func (a *App) hasFlag(flag Flag) bool {
|
||||||
for _, f := range a.Flags {
|
for _, f := range a.Flags {
|
||||||
if flag == f {
|
if flag == f {
|
||||||
|
50
app_test.go
50
app_test.go
@ -8,6 +8,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"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) {
|
func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
|
||||||
app := NewApp()
|
app := NewApp()
|
||||||
app.Action = func(c *Context) {}
|
app.Action = func(c *Context) {}
|
||||||
|
30
category.go
Normal file
30
category.go
Normal 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}})
|
||||||
|
}
|
14
command.go
14
command.go
@ -3,6 +3,7 @@ package cli
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,6 +23,8 @@ type Command struct {
|
|||||||
Description string
|
Description string
|
||||||
// A short description of the arguments of this command
|
// A short description of the arguments of this command
|
||||||
ArgsUsage string
|
ArgsUsage string
|
||||||
|
// The category the command is part of
|
||||||
|
Category string
|
||||||
// The function to call when checking for bash command completions
|
// The function to call when checking for bash command completions
|
||||||
BashComplete func(context *Context)
|
BashComplete func(context *Context)
|
||||||
// An action to execute before any sub-subcommands are run, but after the context is ready
|
// 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.
|
// If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted.
|
||||||
OnUsageError func(context *Context, err error) error
|
OnUsageError func(context *Context, err error) error
|
||||||
// List of child commands
|
// List of child commands
|
||||||
Subcommands []Command
|
Subcommands Commands
|
||||||
// List of flags to parse
|
// List of flags to parse
|
||||||
Flags []Flag
|
Flags []Flag
|
||||||
// Treat all flags as normal arguments if true
|
// Treat all flags as normal arguments if true
|
||||||
@ -59,6 +62,8 @@ func (c Command) FullName() string {
|
|||||||
return strings.Join(c.commandNamePath, " ")
|
return strings.Join(c.commandNamePath, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Commands []Command
|
||||||
|
|
||||||
// Invokes the command given the context, parses ctx.Args() to generate command-specific flags
|
// Invokes the command given the context, parses ctx.Args() to generate command-specific flags
|
||||||
func (c Command) Run(ctx *Context) (err error) {
|
func (c Command) Run(ctx *Context) (err error) {
|
||||||
if len(c.Subcommands) > 0 {
|
if len(c.Subcommands) > 0 {
|
||||||
@ -227,6 +232,13 @@ func (c Command) startApp(ctx *Context) error {
|
|||||||
app.Email = ctx.App.Email
|
app.Email = ctx.App.Email
|
||||||
app.Writer = ctx.App.Writer
|
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
|
// bash completion
|
||||||
app.EnableBashCompletion = ctx.App.EnableBashCompletion
|
app.EnableBashCompletion = ctx.App.EnableBashCompletion
|
||||||
if c.BashComplete != nil {
|
if c.BashComplete != nil {
|
||||||
|
19
help.go
19
help.go
@ -23,9 +23,10 @@ VERSION:
|
|||||||
AUTHOR(S):
|
AUTHOR(S):
|
||||||
{{range .Authors}}{{ . }}{{end}}
|
{{range .Authors}}{{ . }}{{end}}
|
||||||
{{end}}{{if .Commands}}
|
{{end}}{{if .Commands}}
|
||||||
COMMANDS:
|
COMMANDS:{{range .Categories}}{{if .Name}}
|
||||||
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
|
{{.Name}}{{ ":" }}{{end}}{{range .Commands}}
|
||||||
{{end}}{{end}}{{if .Flags}}
|
{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}}
|
||||||
|
{{end}}{{end}}{{if .Flags}}
|
||||||
GLOBAL OPTIONS:
|
GLOBAL OPTIONS:
|
||||||
{{range .Flags}}{{.}}
|
{{range .Flags}}{{.}}
|
||||||
{{end}}{{end}}{{if .Copyright }}
|
{{end}}{{end}}{{if .Copyright }}
|
||||||
@ -41,7 +42,10 @@ var CommandHelpTemplate = `NAME:
|
|||||||
{{.HelpName}} - {{.Usage}}
|
{{.HelpName}} - {{.Usage}}
|
||||||
|
|
||||||
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:
|
||||||
{{.Description}}{{end}}{{if .Flags}}
|
{{.Description}}{{end}}{{if .Flags}}
|
||||||
@ -60,9 +64,10 @@ var SubcommandHelpTemplate = `NAME:
|
|||||||
USAGE:
|
USAGE:
|
||||||
{{.HelpName}} command{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
|
{{.HelpName}} command{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
|
||||||
|
|
||||||
COMMANDS:
|
COMMANDS:{{range .Categories}}{{if .Name}}
|
||||||
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
|
{{.Name}}{{ ":" }}{{end}}{{range .Commands}}
|
||||||
{{end}}{{if .Flags}}
|
{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}}
|
||||||
|
{{end}}{{if .Flags}}
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
{{range .Flags}}{{.}}
|
{{range .Flags}}{{.}}
|
||||||
{{end}}{{end}}
|
{{end}}{{end}}
|
||||||
|
Loading…
Reference in New Issue
Block a user