diff --git a/command.go b/command.go index 4cd907a558..f0cf9a715d 100644 --- a/command.go +++ b/command.go @@ -46,6 +46,9 @@ type Command struct { Flags []Flag `json:"flags"` // Boolean to hide built-in help command and help flag HideHelp bool `json:"hideHelp"` + // Boolean to hide the built-in help command. Applies to this command and + // all of its subcommands: as with HideHelp, a true value is inherited and + // a subcommand cannot turn it back off. // Ignored if HideHelp is true. HideHelpCommand bool `json:"hideHelpCommand"` // Boolean to hide built-in version flag and the VERSION section of help diff --git a/command_setup.go b/command_setup.go index 51c4040039..13ef706315 100644 --- a/command_setup.go +++ b/command_setup.go @@ -227,6 +227,17 @@ func (cmd *Command) hideHelp() bool { return false } +func (cmd *Command) hideHelpCommand() bool { + tracef("hide help command (cmd=%[1]q)", cmd.Name) + for c := cmd; c != nil; c = c.parent { + if c.HideHelpCommand { + return true + } + } + + return false +} + func (cmd *Command) ensureHelp() { tracef("ensuring help (cmd=%[1]q)", cmd.Name) @@ -234,7 +245,7 @@ func (cmd *Command) ensureHelp() { if !cmd.hideHelp() { if cmd.Command(helpCommand.Name) == nil { - if !cmd.HideHelpCommand { + if !cmd.hideHelpCommand() { tracef("appending helpCommand (cmd=%[1]q)", cmd.Name) cmd.appendCommand(helpCommand) } diff --git a/godoc-current.txt b/godoc-current.txt index d680c5f544..b3e9797175 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -464,6 +464,9 @@ type Command struct { Flags []Flag `json:"flags"` // Boolean to hide built-in help command and help flag HideHelp bool `json:"hideHelp"` + // Boolean to hide the built-in help command. Applies to this command and + // all of its subcommands: as with HideHelp, a true value is inherited and + // a subcommand cannot turn it back off. // Ignored if HideHelp is true. HideHelpCommand bool `json:"hideHelpCommand"` // Boolean to hide built-in version flag and the VERSION section of help diff --git a/help_test.go b/help_test.go index a3f3476a86..56e8dfb0ba 100644 --- a/help_test.go +++ b/help_test.go @@ -1271,8 +1271,11 @@ func TestHideHelpCommand_WithHideHelp(t *testing.T) { } func TestHideHelpCommand_WithSubcommands(t *testing.T) { + out := &bytes.Buffer{} cmd := &Command{ HideHelpCommand: true, + Writer: out, + ErrWriter: out, Commands: []*Command{ { Name: "nully", @@ -1289,6 +1292,59 @@ func TestHideHelpCommand_WithSubcommands(t *testing.T) { r.ErrorContains(cmd.Run(buildTestContext(t), []string{"cli.test", "help"}), "No help topic for 'help'") r.NoError(cmd.Run(buildTestContext(t), []string{"cli.test", "--help"})) + + out.Reset() + r.ErrorContains(cmd.Run(buildTestContext(t), []string{"cli.test", "nully", "help"}), "No help topic for 'help'") + r.NoError(cmd.Run(buildTestContext(t), []string{"cli.test", "nully", "--help"})) + r.NotContains(out.String(), "help, h") + + out.Reset() + r.ErrorContains(cmd.Run(buildTestContext(t), []string{"cli.test", "nully", "nully2", "help"}), "No help topic for 'help'") + r.NoError(cmd.Run(buildTestContext(t), []string{"cli.test", "nully", "nully2", "--help"})) + r.NotContains(out.String(), "help, h") +} + +// A subcommand cannot re-enable the help command once an ancestor has hidden +// it. This mirrors HideHelp, which has always been inherited the same way. +func TestHideHelpCommand_SubcommandCannotOptBackIn(t *testing.T) { + out := &bytes.Buffer{} + cmd := &Command{ + HideHelpCommand: true, + Writer: out, + ErrWriter: out, + Commands: []*Command{ + { + Name: "nully", + HideHelpCommand: false, // ignored, the root already hid it + }, + }, + } + + r := require.New(t) + + r.ErrorContains(cmd.Run(buildTestContext(t), []string{"cli.test", "nully", "help"}), "No help topic for 'help'") + r.NoError(cmd.Run(buildTestContext(t), []string{"cli.test", "nully", "--help"})) + r.NotContains(out.String(), "help, h") +} + +// The same inheritance already applies to HideHelp, so the two fields stay +// consistent with each other. +func TestHideHelp_SubcommandCannotOptBackIn(t *testing.T) { + cmd := &Command{ + HideHelp: true, + Writer: io.Discard, + Commands: []*Command{ + { + Name: "nully", + HideHelp: false, // ignored, the root already hid it + }, + }, + } + + r := require.New(t) + + r.ErrorContains(cmd.Run(buildTestContext(t), []string{"cli.test", "nully", "help"}), "No help topic for 'help'") + r.ErrorContains(cmd.Run(buildTestContext(t), []string{"cli.test", "nully", "--help"}), providedButNotDefinedErrMsg) } func TestDefaultCompleteWithFlags(t *testing.T) { diff --git a/testdata/godoc-v3.x.txt b/testdata/godoc-v3.x.txt index d680c5f544..b3e9797175 100644 --- a/testdata/godoc-v3.x.txt +++ b/testdata/godoc-v3.x.txt @@ -464,6 +464,9 @@ type Command struct { Flags []Flag `json:"flags"` // Boolean to hide built-in help command and help flag HideHelp bool `json:"hideHelp"` + // Boolean to hide the built-in help command. Applies to this command and + // all of its subcommands: as with HideHelp, a true value is inherited and + // a subcommand cannot turn it back off. // Ignored if HideHelp is true. HideHelpCommand bool `json:"hideHelpCommand"` // Boolean to hide built-in version flag and the VERSION section of help