Skip to content

Commit 6dfb6e2

Browse files
authored
Merge pull request #906 from hlascelles/add-tree-flag
Add tree command
2 parents e7ed5c2 + 014b0d6 commit 6dfb6e2

File tree

3 files changed

+84
-1
lines changed

3 files changed

+84
-1
lines changed

lib/thor.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,4 +671,37 @@ def help(command = nil, subcommand = false)
671671
self.class.help(shell, subcommand)
672672
end
673673
end
674+
675+
map TREE_MAPPINGS => :tree
676+
677+
desc "tree", "Print a tree of all available commands"
678+
def tree
679+
build_command_tree(self.class, "")
680+
end
681+
682+
private
683+
684+
def build_command_tree(klass, indent)
685+
# Print current class name if it's not the root Thor class
686+
unless klass == Thor
687+
say "#{indent}#{klass.namespace || 'default'}", :blue
688+
indent = "#{indent} "
689+
end
690+
691+
# Print all commands for this class
692+
visible_commands = klass.commands.reject { |_, cmd| cmd.hidden? || cmd.name == "help" }
693+
commands_count = visible_commands.count
694+
visible_commands.sort.each_with_index do |(command_name, command), i|
695+
description = command.description.split("\n").first || ""
696+
icon = i == (commands_count - 1) ? "└─" : "├─"
697+
say "#{indent}#{icon} ", nil, false
698+
say command_name, :green, false
699+
say " (#{description})" unless description.empty?
700+
end
701+
702+
# Print all subcommands (from registered Thor subclasses)
703+
klass.subcommand_classes.each do |_, subclass|
704+
build_command_tree(subclass, indent)
705+
end
706+
end
674707
end

lib/thor/base.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ class Thor
1313
autoload :RakeCompat, File.expand_path("rake_compat", __dir__)
1414
autoload :Group, File.expand_path("group", __dir__)
1515

16-
# Shortcuts for help.
16+
# Shortcuts for help and tree commands.
1717
HELP_MAPPINGS = %w(-h -? --help -D)
18+
TREE_MAPPINGS = %w(-t --tree)
1819

1920
# Thor methods that should not be overwritten by the user.
2021
THOR_RESERVED_WORDS = %w(invoke shell options behavior root destination_root relative_root

spec/tree_spec.rb

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
require "helper"
2+
require "thor"
3+
4+
class TreeApp < Thor
5+
desc "command1", "A top level command"
6+
7+
def command1
8+
end
9+
10+
desc "command2", "Another top level command"
11+
12+
def command2
13+
end
14+
15+
class SubApp < Thor
16+
desc "subcommand1", "A subcommand"
17+
18+
def subcommand1
19+
end
20+
end
21+
22+
desc "sub", "Subcommands"
23+
subcommand "sub", SubApp
24+
end
25+
26+
RSpec.describe "Thor tree command" do
27+
let(:shell) { Thor::Shell::Basic.new }
28+
29+
it "prints a tree of all commands" do
30+
expect(capture(:stdout) { TreeApp.start(["tree"]) }).to match(/├─ command1/)
31+
expect(capture(:stdout) { TreeApp.start(["tree"]) }).to match(/├─ command2/)
32+
expect(capture(:stdout) { TreeApp.start(["tree"]) }).to match(/└─ sub/)
33+
expect(capture(:stdout) { TreeApp.start(["tree"]) }).to match(/subcommand1/)
34+
end
35+
36+
it "includes command descriptions" do
37+
expect(capture(:stdout) { TreeApp.start(["tree"]) }).to match(/A top level command/)
38+
expect(capture(:stdout) { TreeApp.start(["tree"]) }).to match(/Another top level command/)
39+
expect(capture(:stdout) { TreeApp.start(["tree"]) }).to match(/A subcommand/)
40+
end
41+
42+
it "doesn't show hidden commands" do
43+
expect(capture(:stdout) { TreeApp.start(["tree"]) }).not_to match(/help/)
44+
end
45+
46+
it "shows tree command in help" do
47+
expect(capture(:stdout) { TreeApp.start(["help"]) }).to match(/tree.*Print a tree of all available commands/)
48+
end
49+
end

0 commit comments

Comments
 (0)