diff --git a/command.go b/command.go index 4cd907a558..922fe9ee3b 100644 --- a/command.go +++ b/command.go @@ -582,6 +582,18 @@ func (cmd *Command) Path() []string { return []string{cmd.Name} } +// HasHiddenParent returns true if any ancestor of this command has +// Hidden set to true. +func (cmd *Command) HasHiddenParent() bool { + if cmd.parent == nil { + return false + } + if cmd.parent.Hidden { + return true + } + return cmd.parent.HasHiddenParent() +} + // Walk visits cmd and every descendant. If fn returns a non-nil error, the // walk terminates and the error is returned to the caller. func (cmd *Command) Walk(fn func(*Command) error) error { diff --git a/command_test.go b/command_test.go index cad70b7801..63333df5d3 100644 --- a/command_test.go +++ b/command_test.go @@ -6450,3 +6450,56 @@ func TestCommand_Walk_NilFn(t *testing.T) { cmd := &Command{Name: "foo"} assert.Nil(t, cmd.Walk(nil)) } + +func TestCommand_HasHiddenParent(t *testing.T) { + t.Run("root command", func(t *testing.T) { + cmd := &Command{Name: "root"} + assert.False(t, cmd.HasHiddenParent()) + }) + + t.Run("direct hidden parent", func(t *testing.T) { + parent := &Command{Name: "parent", Hidden: true} + child := &Command{Name: "child"} + child.parent = parent + assert.True(t, child.HasHiddenParent()) + }) + + t.Run("non-hidden parent", func(t *testing.T) { + parent := &Command{Name: "parent"} + child := &Command{Name: "child"} + child.parent = parent + assert.False(t, child.HasHiddenParent()) + }) + + t.Run("grandparent is hidden", func(t *testing.T) { + grandparent := &Command{Name: "grandparent", Hidden: true} + parent := &Command{Name: "parent"} + child := &Command{Name: "child"} + parent.parent = grandparent + child.parent = parent + assert.True(t, child.HasHiddenParent()) + }) + + t.Run("subcommands of hidden command are skipped in walk", func(t *testing.T) { + hiddenCmd := &Command{Name: "completion", Hidden: true} + subCmd := &Command{Name: "bash"} + subCmd.parent = hiddenCmd + hiddenCmd.Commands = []*Command{subCmd} + + root := &Command{ + Name: "root", + Commands: []*Command{hiddenCmd}, + } + + var visited []string + err := root.Walk(func(c *Command) error { + if c.Hidden || c.HasHiddenParent() { + return nil + } + visited = append(visited, c.Name) + return nil + }) + require.NoError(t, err) + assert.Equal(t, []string{"root"}, visited) + }) +} diff --git a/godoc-current.txt b/godoc-current.txt index d680c5f544..1a0c96700b 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -618,6 +618,10 @@ func (cmd *Command) FullName() string func (cmd *Command) Generic(name string) Value Generic looks up the value of a local GenericFlag, returns nil if not found +func (cmd *Command) HasHiddenParent() bool + HasHiddenParent returns true if any ancestor of this command has Hidden set + to true. + func (cmd *Command) HasName(name string) bool HasName returns true if Command.Name matches given name diff --git a/testdata/godoc-v3.x.txt b/testdata/godoc-v3.x.txt index d680c5f544..1a0c96700b 100644 --- a/testdata/godoc-v3.x.txt +++ b/testdata/godoc-v3.x.txt @@ -618,6 +618,10 @@ func (cmd *Command) FullName() string func (cmd *Command) Generic(name string) Value Generic looks up the value of a local GenericFlag, returns nil if not found +func (cmd *Command) HasHiddenParent() bool + HasHiddenParent returns true if any ancestor of this command has Hidden set + to true. + func (cmd *Command) HasName(name string) bool HasName returns true if Command.Name matches given name