diff --git a/command.go b/command.go index d19561c2..ad265073 100644 --- a/command.go +++ b/command.go @@ -332,7 +332,7 @@ func (c *Command) SetHelpCommandGroupID(groupID string) { // SetCompletionCommandGroup sets the group id of the completion command. func (c *Command) SetCompletionCommandGroupID(groupID string) { - // completionCommandGroupID is used if no completion commmand is defined by the user + // completionCommandGroupID is used if no completion command is defined by the user c.Root().completionCommandGroupID = groupID } @@ -544,13 +544,16 @@ Aliases: {{.NameAndAliases}}{{end}}{{if .HasExample}} Examples: -{{.Example}}{{end}}{{if .HasAvailableSubCommands}}{{$cmds := .Commands}} +{{.Example}}{{end}}{{if .HasAvailableSubCommands}}{{$cmds := .Commands}}{{if eq (len .Groups) 0}} -Available Commands:{{range $cmds}}{{if (and (eq .GroupID "") (or .IsAvailableCommand (eq .Name "help")))}} - {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{range $group := .Groups}} +Available Commands:{{range $cmds}}{{if (or .IsAvailableCommand (eq .Name "help"))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{else}}{{range $group := .Groups}} {{.Title}}{{range $cmds}}{{if (and (eq .GroupID $group.ID) (or .IsAvailableCommand (eq .Name "help")))}} - {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if not .AllChildCommandsHaveGroup}} + +Additional Commands:{{range $cmds}}{{if (and (eq .GroupID "") (or .IsAvailableCommand (eq .Name "help")))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} Flags: {{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} @@ -1217,7 +1220,7 @@ func (c *Command) AddCommand(cmds ...*Command) { cmds[i].parent = c // if Group is not defined let the developer know right away if x.GroupID != "" && !c.ContainsGroup(x.GroupID) { - panic(fmt.Sprintf("Group id %s is not defined", x.GroupID)) + panic(fmt.Sprintf("Group id '%s' is not defined for subcommand '%s'", x.GroupID, cmds[i].CommandPath())) } // update max lengths usageLen := len(x.Use) @@ -1246,6 +1249,16 @@ func (c *Command) Groups() []*Group { return c.commandgroups } +// AllChildCommandsHaveGroup returns if all subcommands are assigned to a group +func (c *Command) AllChildCommandsHaveGroup() bool { + for _, sub := range c.commands { + if (sub.IsAvailableCommand() || sub == c.helpCommand) && sub.GroupID == "" { + return false + } + } + return true +} + // ContainGroups return if groupID exists in the list of command groups. func (c *Command) ContainsGroup(groupID string) bool { for _, x := range c.commandgroups { diff --git a/command_test.go b/command_test.go index 0060bfce..0adec93d 100644 --- a/command_test.go +++ b/command_test.go @@ -1783,7 +1783,7 @@ func TestUsageWithGroup(t *testing.T) { } // help should be ungrouped here - checkStringContains(t, output, "\nAvailable Commands:\n help") + checkStringContains(t, output, "\nAdditional Commands:\n help") checkStringContains(t, output, "\ngroup1\n cmd1") checkStringContains(t, output, "\ngroup2\n cmd2") } @@ -1802,11 +1802,11 @@ func TestUsageHelpGroup(t *testing.T) { } // now help should be grouped under "group" - checkStringOmits(t, output, "\nAvailable Commands:\n help") - checkStringContains(t, output, "\nAvailable Commands:\n\ngroup\n help") + checkStringOmits(t, output, "\nAdditional Commands:\n help") + checkStringContains(t, output, "\ngroup\n help") } -func TestUsageCompletionpGroup(t *testing.T) { +func TestUsageCompletionGroup(t *testing.T) { var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun} rootCmd.AddGroup(&Group{ID: "group", Title: "group"}) @@ -1822,8 +1822,30 @@ func TestUsageCompletionpGroup(t *testing.T) { } // now completion should be grouped under "group" - checkStringOmits(t, output, "\nAvailable Commands:\n completion") - checkStringContains(t, output, "\nAvailable Commands:\n\ngroup\n completion") + checkStringOmits(t, output, "\nAdditional Commands:\n completion") + checkStringContains(t, output, "\ngroup\n completion") +} + +func TestUngroupedCommand(t *testing.T) { + var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun} + + rootCmd.AddGroup(&Group{ID: "group", Title: "group"}) + rootCmd.AddGroup(&Group{ID: "help", Title: "help"}) + + rootCmd.AddCommand(&Command{Use: "xxx", GroupID: "group", Run: emptyRun}) + rootCmd.SetHelpCommandGroupID("help") + rootCmd.SetCompletionCommandGroupID("group") + + // Add a command without a group + rootCmd.AddCommand(&Command{Use: "yyy", Run: emptyRun}) + + output, err := executeCommand(rootCmd, "--help") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // The yyy command should be in the additional command "group" + checkStringContains(t, output, "\nAdditional Commands:\n yyy") } func TestAddGroup(t *testing.T) {