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
33 changes: 33 additions & 0 deletions lib/thor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -671,4 +671,37 @@ def help(command = nil, subcommand = false)
self.class.help(shell, subcommand)
end
end

map TREE_MAPPINGS => :tree

desc "tree", "Print a tree of all available commands"
def tree
build_command_tree(self.class, "")
end

private

def build_command_tree(klass, indent)
# Print current class name if it's not the root Thor class
unless klass == Thor
say "#{indent}#{klass.namespace || 'default'}", :blue
indent = "#{indent} "
end

# Print all commands for this class
visible_commands = klass.commands.reject { |_, cmd| cmd.hidden? || cmd.name == "help" }
commands_count = visible_commands.count
visible_commands.sort.each_with_index do |(command_name, command), i|
description = command.description.split("\n").first || ""
icon = i == (commands_count - 1) ? "└─" : "├─"
say "#{indent}#{icon} ", nil, false
say command_name, :green, false
say " (#{description})" unless description.empty?
end

# Print all subcommands (from registered Thor subclasses)
klass.subcommand_classes.each do |_, subclass|
build_command_tree(subclass, indent)
end
end
end
3 changes: 2 additions & 1 deletion lib/thor/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ class Thor
autoload :RakeCompat, File.expand_path("rake_compat", __dir__)
autoload :Group, File.expand_path("group", __dir__)

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

# Thor methods that should not be overwritten by the user.
THOR_RESERVED_WORDS = %w(invoke shell options behavior root destination_root relative_root
Expand Down
49 changes: 49 additions & 0 deletions spec/tree_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
require "helper"
require "thor"

class TreeApp < Thor
desc "command1", "A top level command"

def command1
end

desc "command2", "Another top level command"

def command2
end

class SubApp < Thor
desc "subcommand1", "A subcommand"

def subcommand1
end
end

desc "sub", "Subcommands"
subcommand "sub", SubApp
end

RSpec.describe "Thor tree command" do
let(:shell) { Thor::Shell::Basic.new }

it "prints a tree of all commands" do
expect(capture(:stdout) { TreeApp.start(["tree"]) }).to match(/├─ command1/)
expect(capture(:stdout) { TreeApp.start(["tree"]) }).to match(/├─ command2/)
expect(capture(:stdout) { TreeApp.start(["tree"]) }).to match(/└─ sub/)
expect(capture(:stdout) { TreeApp.start(["tree"]) }).to match(/subcommand1/)
end

it "includes command descriptions" do
expect(capture(:stdout) { TreeApp.start(["tree"]) }).to match(/A top level command/)
expect(capture(:stdout) { TreeApp.start(["tree"]) }).to match(/Another top level command/)
expect(capture(:stdout) { TreeApp.start(["tree"]) }).to match(/A subcommand/)
end

it "doesn't show hidden commands" do
expect(capture(:stdout) { TreeApp.start(["tree"]) }).not_to match(/help/)
end

it "shows tree command in help" do
expect(capture(:stdout) { TreeApp.start(["help"]) }).to match(/tree.*Print a tree of all available commands/)
end
end