Skip to content

(2 of 4) Add Tree: a read-only view of CLI's commands (if accepted, must be stack-merged onto #175) - #173

Open
kigster wants to merge 6 commits into
dry-rb:mainfrom
kigster:kig/command-tree-introspection
Open

kigster wants to merge 6 commits into
dry-rb:mainfrom
kigster:kig/command-tree-introspection

Conversation

@kigster

@kigster kigster commented Oct 8, 2026 •

Copy link
Copy Markdown

Proposal 2 of 4 in the dry-cli extension hooks series. This should stack on top of #175. As mentioned, I couldn't select a branch on a fork as the base, but perhaps the owners can.

Summary

A public, read-only, live view of the commands, replacing the CommandRegistry / LookupResult / Node internals that help completion gems walk today.

# Registry#tree   (also Dry::CLI#tree, Tree.for(command))
root = MyApp::Commands.tree            

root["db"]                             # by name or alias
root.dig("db", "migrate")
node, rest = root.resolve(%w[db migrate --force])  # same lookup as dispatch
root.walk(hidden: false) { |node| … }
node.options.first                     # Tree::Param: name, kind, type, desc, values, switches, metadata…
 CommandRegistry
-  #get(arguments)            # lookup inlined under the mutex
+  #get(arguments)            # synchronize { .lookup(@root, arguments) }
+  .lookup(node, arguments)   # shared with Tree::Node#resolve
+  #root
  • Live: reads go to the registry, so commands and options added after tree was taken are visible (needed for dynamic completion).

  • Param#metadata carries every declared key, so extensions read their own (i.e., file: true).

  • CommandRegistry, Option, Argument stay @api private.

Evidence

  • Before: dry-cli-autocomplete: registry.get(path).children.reject { _2.hidden }; dry-cli-help: node.parent.aliases.filter_map { … }.

  • After: tree.walk(hidden: false) and node.aliases. spec/unit/dry/cli/tree_spec.rb: 30 examples, including resolve-vs-dispatch parity over 10 command lines and liveness. Full suite 661 examples, 0 failures; rubocop clean.

Merge Risk

Additive; CommandRegistry#get is refactored but behaves identically (whole suite unchanged).

Blast Radius

Believed to be small.

If merged, what changes in the futue:

  • New public surface (Tree) that dry-cli would need to commit to keeping stable.

Dry::CLI#call and Command.new take `kernel:`, defaulting to Kernel. Help,
errors and typos stop the CLI by raising a private Halt, which #call turns
into kernel.exit, so a kernel whose exit returns (as Aruba's does) still
stops the CLI. A command's own `exit` goes through its kernel too.

Dry::CLI::Launcher[registry] runs a CLI with the arguments, streams and
kernel in the order Aruba's in-process launcher passes them, so a CLI can
be tested in the same process with StringIO streams.
Registry#tree, Dry::CLI#tree and Dry::CLI::Tree.for return a live view of
the registered commands, for gems that describe a CLI rather than run it:
help screens, shell completion, documentation generators. Until now they
could only walk CommandRegistry internals.

Tree::Node#resolve shares its lookup with dispatch (CommandRegistry.lookup),
so the command it finds is the one the CLI would run. Tree::Param is a
frozen snapshot of an option or argument, including every declared key.
One expectation per example, no local variables, described_class for the type under test.
metadata, desc, default and aliases shared their arrays, hashes and strings with the Option declaration, so appending to metadata[:aliases] added a live switch to the CLI. Each is now a frozen copy.
tmp, args, i and numbered block parameters become child, arguments, index and named parameters. CommandRegistry.lookup also folds its two not-found branches into one; behavior is unchanged.
@kigster kigster changed the title Add Tree, a read-only view of a CLI's commands (2/4) — needs to be stacked Add Tree, a read-only view of a CLI's commands (2/4) — needs to be stacked on kigster:kig/auto-inject-compatibility Oct 8, 2026
@kigster kigster changed the title Add Tree, a read-only view of a CLI's commands (2/4) — needs to be stacked on kigster:kig/auto-inject-compatibility Add Tree, a read-only view of a CLI's commands (2 of 4) — needs to be stacked on kigster:kig/auto-inject-compatibility Oct 8, 2026
@kigster kigster changed the title Add Tree, a read-only view of a CLI's commands (2 of 4) — needs to be stacked on kigster:kig/auto-inject-compatibility Add Tree, a read-only view of a CLI's commands (2 of 4) — needs to be stacked on kigster:kig/auto-inject-compatibility Oct 8, 2026
@kigster kigster changed the title Add Tree, a read-only view of a CLI's commands (2 of 4) — needs to be stacked on kigster:kig/auto-inject-compatibility (2 of 4) Add Tree, a read-only view of a CLI's commands — needs to be stacked on kigster:kig/auto-inject-compatibility Oct 8, 2026
@kigster kigster changed the title (2 of 4) Add Tree, a read-only view of a CLI's commands — needs to be stacked on kigster:kig/auto-inject-compatibility (2 of 4) Add Tree, a read-only view of a CLI's commands (if accepted, must to be stacked on top of kigster:kig/auto-inject-compatibility) Oct 8, 2026
@kigster kigster changed the title (2 of 4) Add Tree, a read-only view of a CLI's commands (if accepted, must to be stacked on top of kigster:kig/auto-inject-compatibility) (2 of 4) Add Tree: a read-only view of CLI's commands (if accepted, must be stack-merged onto #175) Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant