Skip to content
This repository has been archived by the owner on Nov 24, 2022. It is now read-only.

Commit

Permalink
Merge pull request #1 from aawsome/add-groups
Browse files Browse the repository at this point in the history
feature: groups
  • Loading branch information
kimskimchi authored Mar 18, 2022
2 parents 5d066b7 + f6d342a commit 3e9cada
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Cobra provides:
* Global, local and cascading flags
* Intelligent suggestions (`app srver`... did you mean `app server`?)
* Automatic help generation for commands and flags
* Grouping help for subcommands
* Automatic help flag recognition of `-h`, `--help`, etc.
* Automatically generated shell autocomplete for your application (bash, zsh, fish, powershell)
* Automatically generated man pages for your application
Expand Down
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 @@ -288,6 +303,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 @@ -496,10 +520,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 @@ -1109,6 +1136,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 @@ -1147,6 +1175,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 @@ -1169,6 +1201,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
49 changes: 49 additions & 0 deletions command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1617,6 +1617,55 @@ func TestEnableCommandSortingIsDisabled(t *testing.T) {
EnableCommandSorting = true
}

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

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.CompletionOptions.DisableDefaultCmd = true

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 @@ -447,6 +447,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 3e9cada

Please sign in to comment.