Skip to content

Commit dc13fc6

Browse files
committed
Merge pull request #391 from anthonycrumley/fix-subcommand-edgecases
Fix dispatching of subcommands (concerning :help and *args) (#374)
2 parents 34a4fcb + 68b4edc commit dc13fc6

File tree

2 files changed

+50
-29
lines changed

2 files changed

+50
-29
lines changed

lib/thor.rb

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -321,23 +321,14 @@ def stop_on_unknown_option #:nodoc:
321321

322322
# The method responsible for dispatching given the args.
323323
def dispatch(meth, given_args, given_opts, config) #:nodoc: # rubocop:disable MethodLength
324-
# There is an edge case when dispatching from a subcommand.
325-
# A problem occurs invoking the default command. This case occurs
326-
# when arguments are passed and a default command is defined, and
327-
# the first given_args does not match the default command.
328-
# Thor use "help" by default so we skip that case.
329-
# Note the call to retrieve_command_name. It's called with
330-
# given_args.dup since that method calls args.shift. Then lookup
331-
# the command normally. If the first item in given_args is not
332-
# a command then use the default command. The given_args will be
333-
# intact later since dup was used.
334-
if config[:invoked_via_subcommand] && given_args.size >= 1 && default_command != 'help' && given_args.first != default_command
335-
meth ||= retrieve_command_name(given_args.dup)
336-
command = all_commands[normalize_command_name(meth)]
337-
command ||= all_commands[normalize_command_name(default_command)]
338-
else
339-
meth ||= retrieve_command_name(given_args)
340-
command = all_commands[normalize_command_name(meth)]
324+
meth ||= retrieve_command_name(given_args)
325+
command = all_commands[normalize_command_name(meth)]
326+
327+
if !command && config[:invoked_via_subcommand]
328+
# We're a subcommand and our first argument didn't match any of our
329+
# commands. So we put it back and call our default command.
330+
given_args.unshift(meth)
331+
command = all_commands[normalize_command_name(default_command)]
341332
end
342333

343334
if command

spec/register_spec.rb

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,20 @@ def say
8282
default_command :say
8383
end
8484

85+
class SubcommandWithDefault < Thor
86+
default_command :default
87+
88+
desc 'default', 'default subcommand'
89+
def default
90+
puts 'default'
91+
end
92+
93+
desc 'with_args', 'subcommand with arguments'
94+
def with_args(*args)
95+
puts 'received arguments: ' + args.join(',')
96+
end
97+
end
98+
8599
BoringVendorProvidedCLI.register(
86100
ExcitingPluginCLI,
87101
'exciting',
@@ -125,6 +139,9 @@ def say
125139
'say message',
126140
'subcommands ftw')
127141

142+
BoringVendorProvidedCLI.register(SubcommandWithDefault,
143+
'subcommand', 'subcommand', 'Run subcommands')
144+
128145
describe '.register-ing a Thor subclass' do
129146
it 'registers the plugin as a subcommand' do
130147
fireworks_output = capture(:stdout) { BoringVendorProvidedCLI.start(%w[exciting fireworks]) }
@@ -136,20 +153,33 @@ def say
136153
expect(help_output).to include('do exciting things')
137154
end
138155

139-
it 'invokes the default command correctly' do
140-
output = capture(:stdout) { BoringVendorProvidedCLI.start(%w[say hello]) }
141-
expect(output).to include('hello')
142-
end
156+
context 'with a default command,' do
157+
it 'invokes the default command correctly' do
158+
output = capture(:stdout) { BoringVendorProvidedCLI.start(%w[say hello]) }
159+
expect(output).to include('hello')
160+
end
143161

144-
it 'invokes the default command correctly with multiple args' do
145-
output = capture(:stdout) { BoringVendorProvidedCLI.start(%w[say_multiple hello adam]) }
146-
expect(output).to include('hello')
147-
expect(output).to include('adam')
148-
end
162+
it 'invokes the default command correctly with multiple args' do
163+
output = capture(:stdout) { BoringVendorProvidedCLI.start(%w[say_multiple hello adam]) }
164+
expect(output).to include('hello')
165+
expect(output).to include('adam')
166+
end
167+
168+
it 'invokes the default command correctly with a declared argument' do
169+
output = capture(:stdout) { BoringVendorProvidedCLI.start(%w[say_argument hello]) }
170+
expect(output).to include('hello')
171+
end
172+
173+
it "displays the subcommand's help message" do
174+
output = capture(:stdout) { BoringVendorProvidedCLI.start(%w[subcommand help]) }
175+
expect(output).to include('default subcommand')
176+
expect(output).to include('subcommand with argument')
177+
end
149178

150-
it 'invokes the default command correctly with a declared argument' do
151-
output = capture(:stdout) { BoringVendorProvidedCLI.start(%w[say_argument hello]) }
152-
expect(output).to include('hello')
179+
it "invokes commands with their actual args" do
180+
output = capture(:stdout) { BoringVendorProvidedCLI.start(%w[subcommand with_args actual_argument]) }
181+
expect(output.strip).to eql('received arguments: actual_argument')
182+
end
153183
end
154184

155185
context 'when $thor_runner is false' do

0 commit comments

Comments
 (0)