Skip to content

Add Command.Walk(fn) and Command.Path() convenience methods #2324

@coilysiren

Description

@coilysiren

Summary

Add two small convenience methods to *cli.Command that several third-party extensions reimplement today:

// Walk visits cmd and every descendant in pre-order. Returning a non-nil
// error from fn short-circuits the walk and is returned to the caller.
func (cmd *Command) Walk(fn func(*Command) error) error

// Path returns the path of command names from the root to cmd, inclusive.
// Each element is a Command.Name. Useful for tools that need to manipulate
// the segments (dot-joined for tool names, slash-joined for URLs, etc).
// FullName(), which already exists, is equivalent to strings.Join(cmd.Path(), " ").
func (cmd *Command) Path() []string

Motivation

I maintain a family of small urfave/cli extensions where each repo independently reimplements both of these:

  • cli-mcp — projects a command tree as an MCP server. registerTree is a hand-rolled walker; tool names are dot-joined paths.
  • cli-web-docs — emits static HTML docs. buildNavList and renderPerPage each walk the tree; nav slugs use dash-joined paths.
  • cli-web-ops — mobile-first MCP-client web UI. Walks an MCP tool surface, but the underlying motivation is the same.

The pattern across all three is:

var walk func(prefix []string, cmd *cli.Command)
walk = func(prefix []string, cmd *cli.Command) {
    if cmd.Hidden { return }
    path := append(prefix, cmd.Name)
    // ... do per-command work using `path` ...
    for _, sub := range cmd.Commands {
        walk(path, sub)
    }
}
walk(nil, root)

Three flavors of the same thing. Walk removes the recursion boilerplate; Path() removes the manual append(prefix, cmd.Name) accumulation. Both are non-breaking additions.

Alternatives considered

  • Just keep the local reimplementations. Has worked, but the third extension to need it (cli-web-ops) shipped before promotion. Two reasonable choices for ergonomic extension surface seems like the threshold.
  • A separate urfave/cli-walk helper module. Possible but seems silly for ~15 lines of code that fit cleanly on *Command.

Happy to send a PR if the team is interested.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions