Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 12 additions & 1 deletion command_setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,14 +227,25 @@ func (cmd *Command) hideHelp() bool {
return false
}

func (cmd *Command) hideHelpCommand() bool {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: with this change HideHelpCommand now propagates to all descendants, but its doc comment in command.go still reads // Ignored if HideHelp is true. only. A short note that the value applies to this command and its subcommands would prevent confusion about why a nested command lost its help command.

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)

helpCommand := buildHelpCommand(true)

if !cmd.hideHelp() {
if cmd.Command(helpCommand.Name) == nil {
if !cmd.HideHelpCommand {
if !cmd.hideHelpCommand() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heads-up: this is a behavior change for nested trees, not just a bug fix. hideHelpCommand() walks the whole ancestor chain, so once any ancestor sets HideHelpCommand: true, a descendant cannot re-enable the built-in help command — even by explicitly setting HideHelpCommand: false on itself. Before this change, app config help still worked when only the root hid the command, because each command's own field value controlled the append. If that was intentional (matching HideHelp inheritance semantics), consider noting it explicitly in the release notes.

tracef("appending helpCommand (cmd=%[1]q)", cmd.Name)
cmd.appendCommand(helpCommand)
}
Expand Down
3 changes: 3 additions & 0 deletions godoc-current.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
56 changes: 56 additions & 0 deletions help_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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) {
Expand Down
3 changes: 3 additions & 0 deletions testdata/godoc-v3.x.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down