Skip to content

Commit b43be99

Browse files
jpmcbmarckhouzam
andauthored
Check for group presence after full initialization (#1839) (#1841)
Fixes #1831 By moving the check for help group existence to "ExecuteC()" we no longer need groups to be added before AddCommand() is called. This provides more flexibility to developers and works better with the use of "init()" for command creation. Signed-off-by: Marc Khouzam <[email protected]> Signed-off-by: Marc Khouzam <[email protected]> Co-authored-by: Marc Khouzam <[email protected]>
1 parent 8607918 commit b43be99

File tree

3 files changed

+100
-8
lines changed

3 files changed

+100
-8
lines changed

command.go

+17-4
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,10 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
998998
// initialize completion at the last point to allow for user overriding
999999
c.InitDefaultCompletionCmd()
10001000

1001+
// Now that all commands have been created, let's make sure all groups
1002+
// are properly created also
1003+
c.checkCommandGroups()
1004+
10011005
args := c.args
10021006

10031007
// Workaround FAIL with "go test -v" or "cobra.test -test.v", see #155
@@ -1092,6 +1096,19 @@ func (c *Command) ValidateRequiredFlags() error {
10921096
return nil
10931097
}
10941098

1099+
// checkCommandGroups checks if a command has been added to a group that does not exists.
1100+
// If so, we panic because it indicates a coding error that should be corrected.
1101+
func (c *Command) checkCommandGroups() {
1102+
for _, sub := range c.commands {
1103+
// if Group is not defined let the developer know right away
1104+
if sub.GroupID != "" && !c.ContainsGroup(sub.GroupID) {
1105+
panic(fmt.Sprintf("group id '%s' is not defined for subcommand '%s'", sub.GroupID, sub.CommandPath()))
1106+
}
1107+
1108+
sub.checkCommandGroups()
1109+
}
1110+
}
1111+
10951112
// InitDefaultHelpFlag adds default help flag to c.
10961113
// It is called automatically by executing the c or by calling help and usage.
10971114
// If c already has help flag, it will do nothing.
@@ -1218,10 +1235,6 @@ func (c *Command) AddCommand(cmds ...*Command) {
12181235
panic("Command can't be a child of itself")
12191236
}
12201237
cmds[i].parent = c
1221-
// if Group is not defined let the developer know right away
1222-
if x.GroupID != "" && !c.ContainsGroup(x.GroupID) {
1223-
panic(fmt.Sprintf("Group id '%s' is not defined for subcommand '%s'", x.GroupID, cmds[i].CommandPath()))
1224-
}
12251238
// update max lengths
12261239
usageLen := len(x.Use)
12271240
if usageLen > c.commandsMaxUseLen {

command_test.go

+78
Original file line numberDiff line numberDiff line change
@@ -1862,6 +1862,84 @@ func TestAddGroup(t *testing.T) {
18621862
checkStringContains(t, output, "\nTest group\n cmd")
18631863
}
18641864

1865+
func TestWrongGroupFirstLevel(t *testing.T) {
1866+
var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun}
1867+
1868+
rootCmd.AddGroup(&Group{ID: "group", Title: "Test group"})
1869+
// Use the wrong group ID
1870+
rootCmd.AddCommand(&Command{Use: "cmd", GroupID: "wrong", Run: emptyRun})
1871+
1872+
defer func() {
1873+
if recover() == nil {
1874+
t.Errorf("The code should have panicked due to a missing group")
1875+
}
1876+
}()
1877+
_, err := executeCommand(rootCmd, "--help")
1878+
if err != nil {
1879+
t.Errorf("Unexpected error: %v", err)
1880+
}
1881+
}
1882+
1883+
func TestWrongGroupNestedLevel(t *testing.T) {
1884+
var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun}
1885+
var childCmd = &Command{Use: "child", Run: emptyRun}
1886+
rootCmd.AddCommand(childCmd)
1887+
1888+
childCmd.AddGroup(&Group{ID: "group", Title: "Test group"})
1889+
// Use the wrong group ID
1890+
childCmd.AddCommand(&Command{Use: "cmd", GroupID: "wrong", Run: emptyRun})
1891+
1892+
defer func() {
1893+
if recover() == nil {
1894+
t.Errorf("The code should have panicked due to a missing group")
1895+
}
1896+
}()
1897+
_, err := executeCommand(rootCmd, "child", "--help")
1898+
if err != nil {
1899+
t.Errorf("Unexpected error: %v", err)
1900+
}
1901+
}
1902+
1903+
func TestWrongGroupForHelp(t *testing.T) {
1904+
var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun}
1905+
var childCmd = &Command{Use: "child", Run: emptyRun}
1906+
rootCmd.AddCommand(childCmd)
1907+
1908+
rootCmd.AddGroup(&Group{ID: "group", Title: "Test group"})
1909+
// Use the wrong group ID
1910+
rootCmd.SetHelpCommandGroupID("wrong")
1911+
1912+
defer func() {
1913+
if recover() == nil {
1914+
t.Errorf("The code should have panicked due to a missing group")
1915+
}
1916+
}()
1917+
_, err := executeCommand(rootCmd, "--help")
1918+
if err != nil {
1919+
t.Errorf("Unexpected error: %v", err)
1920+
}
1921+
}
1922+
1923+
func TestWrongGroupForCompletion(t *testing.T) {
1924+
var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun}
1925+
var childCmd = &Command{Use: "child", Run: emptyRun}
1926+
rootCmd.AddCommand(childCmd)
1927+
1928+
rootCmd.AddGroup(&Group{ID: "group", Title: "Test group"})
1929+
// Use the wrong group ID
1930+
rootCmd.SetCompletionCommandGroupID("wrong")
1931+
1932+
defer func() {
1933+
if recover() == nil {
1934+
t.Errorf("The code should have panicked due to a missing group")
1935+
}
1936+
}()
1937+
_, err := executeCommand(rootCmd, "--help")
1938+
if err != nil {
1939+
t.Errorf("Unexpected error: %v", err)
1940+
}
1941+
}
1942+
18651943
func TestSetOutput(t *testing.T) {
18661944
c := &Command{}
18671945
c.SetOutput(nil)

user_guide.md

+5-4
Original file line numberDiff line numberDiff line change
@@ -492,10 +492,11 @@ around it. In fact, you can provide your own if you want.
492492

493493
### Grouping commands in help
494494

495-
Cobra supports grouping of available commands. Groups must be explicitly defined by `AddGroup` and set by
496-
the `GroupId` element of a subcommand. The groups will appear in the same order as they are defined.
497-
If you use the generated `help` or `completion` commands, you can set the group ids by `SetHelpCommandGroupId`
498-
and `SetCompletionCommandGroupId`, respectively.
495+
Cobra supports grouping of available commands in the help output. To group commands, each group must be explicitly
496+
defined using `AddGroup()` on the parent command. Then a subcommand can be added to a group using the `GroupID` element
497+
of that subcommand. The groups will appear in the help output in the same order as they are defined using different
498+
calls to `AddGroup()`. If you use the generated `help` or `completion` commands, you can set their group ids using
499+
`SetHelpCommandGroupId()` and `SetCompletionCommandGroupId()` on the root command, respectively.
499500

500501
### Defining your own help
501502

0 commit comments

Comments
 (0)