Skip to content

Commit

Permalink
Add groups for commands in help
Browse files Browse the repository at this point in the history
Adds the possibility to group the commands in the help message.

Merge spf13/cobra#1003

closes #836
closes #1271
  • Loading branch information
aawsome authored and hoshsadiq committed Feb 8, 2022
1 parent d910441 commit 27c1d51
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 3 deletions.
58 changes: 55 additions & 3 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ import (
// FParseErrWhitelist configures Flag parse errors to be ignored
type FParseErrWhitelist flag.ParseErrorsWhitelist

// Structure to manage groups for commands
type Group struct {
Group string
Title string
}

// Command is just that, a command for your application.
// E.g. 'go run ...' - 'run' is the command. Cobra requires
// you to define the usage and description as part of your command
Expand All @@ -57,6 +63,9 @@ type Command struct {
// Short is the short description shown in the 'help' output.
Short string

// The group under which the command is grouped in the 'help' output.
Group string

// Long is the long message shown in the 'help <this-command>' output.
Long string

Expand Down Expand Up @@ -124,6 +133,9 @@ type Command struct {
// PersistentPostRunE: PersistentPostRun but returns an error.
PersistentPostRunE func(cmd *Command, args []string) error

// groups for commands
commandgroups []*Group

// args is actual args parsed from flags.
args []string
// flagErrorBuf contains all error messages from pflag.
Expand Down Expand Up @@ -156,6 +168,9 @@ type Command struct {
// helpCommand is command with usage 'help'. If it's not defined by user,
// cobra uses default help command.
helpCommand *Command
// helpCommandGroup is the default group the helpCommand is in
helpCommandGroup string

// versionTemplate is the version template defined by user.
versionTemplate string

Expand Down Expand Up @@ -297,6 +312,15 @@ func (c *Command) SetHelpCommand(cmd *Command) {
c.helpCommand = cmd
}

// SetHelpCommandGroup sets the group of the help command.
func (c *Command) SetHelpCommandGroup(group string) {
if c.helpCommand != nil {
c.helpCommand.Group = group
}
// helpCommandGroup is used if no helpCommand is defined by the user
c.helpCommandGroup = group
}

// SetHelpTemplate sets help template to be used. Application can use it to set custom template.
func (c *Command) SetHelpTemplate(s string) {
c.helpTemplate = s
Expand Down Expand Up @@ -510,10 +534,13 @@ Aliases:
{{.NameAndAliases}}{{end}}{{if .HasExample}}
Examples:
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}{{$cmds := .Commands}}
Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
Available Commands:{{range $cmds}}{{if (and (eq .Group "") (or .IsAvailableCommand (eq .Name "help")))}}
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{range $group := .Groups}}
{{.Title}}{{range $cmds}}{{if (and (eq .Group $group.Group) (or .IsAvailableCommand (eq .Name "help")))}}
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
Flags:
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
Expand Down Expand Up @@ -1137,6 +1164,7 @@ Simply type ` + c.Name() + ` help [path to command] for full details.`,
CheckErr(cmd.Help())
}
},
Group: c.helpCommandGroup,
}
}
c.RemoveCommand(c.helpCommand)
Expand Down Expand Up @@ -1175,6 +1203,10 @@ func (c *Command) AddCommand(cmds ...*Command) {
panic("Command can't be a child of itself")
}
cmds[i].parent = c
// if Group is not defined generate a new one with same title
if x.Group != "" && !c.ContainsGroup(x.Group) {
c.AddGroup(&Group{Group: x.Group, Title: x.Group})
}
// update max lengths
usageLen := len(x.Use)
if usageLen > c.commandsMaxUseLen {
Expand All @@ -1197,6 +1229,26 @@ func (c *Command) AddCommand(cmds ...*Command) {
}
}

// Groups returns a slice of child command groups.
func (c *Command) Groups() []*Group {
return c.commandgroups
}

// ContainGroups return if group is in command groups.
func (c *Command) ContainsGroup(group string) bool {
for _, x := range c.commandgroups {
if x.Group == group {
return true
}
}
return false
}

// AddGroup adds one or more command groups to this parent command.
func (c *Command) AddGroup(groups ...*Group) {
c.commandgroups = append(c.commandgroups, groups...)
}

// RemoveCommand removes one or more commands from a parent command.
func (c *Command) RemoveCommand(cmds ...*Command) {
commands := []*Command{}
Expand Down
47 changes: 47 additions & 0 deletions command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1639,6 +1639,53 @@ func TestEnableCommandSortingIsDisabled(t *testing.T) {
EnableCommandSorting = true
}

func TestUsageWithGroup(t *testing.T) {
var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun}

rootCmd.AddCommand(&Command{Use: "cmd1", Group: "group1", Run: emptyRun})
rootCmd.AddCommand(&Command{Use: "cmd2", Group: "group2", Run: emptyRun})

output, err := executeCommand(rootCmd, "--help")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}

// help should be ungrouped here
checkStringContains(t, output, "\nAvailable Commands:\n help")
checkStringContains(t, output, "\ngroup1\n cmd1")
checkStringContains(t, output, "\ngroup2\n cmd2")
}

func TestUsageHelpGroup(t *testing.T) {
var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun}

rootCmd.AddCommand(&Command{Use: "xxx", Group: "group", Run: emptyRun})
rootCmd.SetHelpCommandGroup("group")

output, err := executeCommand(rootCmd, "--help")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}

// now help should be grouped under "group"
checkStringOmits(t, output, "\nAvailable Commands:\n help")
checkStringContains(t, output, "\nAvailable Commands:\n\ngroup\n help")
}

func TestAddGroup(t *testing.T) {
var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun}

rootCmd.AddGroup(&Group{Group: "group", Title: "Test group"})
rootCmd.AddCommand(&Command{Use: "cmd", Group: "group", Run: emptyRun})

output, err := executeCommand(rootCmd, "--help")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}

checkStringContains(t, output, "\nTest group\n cmd")
}

func TestSetOutput(t *testing.T) {
c := &Command{}
c.SetOutput(nil)
Expand Down
5 changes: 5 additions & 0 deletions user_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,11 @@ command and flag definitions are needed.
Help is just a command like any other. There is no special logic or behavior
around it. In fact, you can provide your own if you want.

### Grouping commands in help

Cobra supports grouping of available commands. Groups can either be explicitly defined by `AddGroup` and set by
the `Group` element of a subcommand. If Groups are not explicitly defined they are implicitly defined.

### Defining your own help

You can provide your own Help command or your own template for the default command to use
Expand Down

0 comments on commit 27c1d51

Please sign in to comment.