diff --git a/lib/thor.rb b/lib/thor.rb index 480c346ec..703ebb805 100644 --- a/lib/thor.rb +++ b/lib/thor.rb @@ -210,7 +210,7 @@ def subcommand(subcommand, subcommand_class) define_method(subcommand) do |*args| args, opts = Thor::Arguments.split(args) - invoke subcommand_class, args, opts + invoke subcommand_class, args, opts, :invoked_via_subcommand => true end end @@ -255,8 +255,24 @@ def check_unknown_options?(config) #:nodoc: # The method responsible for dispatching given the args. def dispatch(meth, given_args, given_opts, config) #:nodoc: - meth ||= retrieve_task_name(given_args) - task = all_tasks[normalize_task_name(meth)] + # There is an edge case when dispatching from a subcommand. + # A problem occurs invoking the default task. This case occurs + # when arguments are passed and a default task is defined, and + # the first given_args does not match the default task. + # Thor use "help" by default so we skip that case. + # Note the call to retrieve_task_name. It's called with + # given_args.dup since that method calls args.shift. Then lookup + # the task normally. If the first item in given_args is not + # a task then use the default task. The given_args will be + # intact later since dup was used. + if config[:invoked_via_subcommand] && given_args.size >= 1 && default_task != "help" && given_args.first != default_task + meth ||= retrieve_task_name(given_args.dup) + task = all_tasks[normalize_task_name(meth)] + task ||= all_tasks[normalize_task_name(default_task)] + else + meth ||= retrieve_task_name(given_args) + task = all_tasks[normalize_task_name(meth)] + end if task args, opts = Thor::Options.split(given_args) diff --git a/spec/register_spec.rb b/spec/register_spec.rb index 8f6464308..0b05fda6a 100644 --- a/spec/register_spec.rb +++ b/spec/register_spec.rb @@ -54,6 +54,34 @@ def animal end end +class PluginWithDefault < Thor + desc "say MSG", "print MSG" + def say(msg) + puts msg + end + + default_task :say +end + +class PluginWithDefaultMultipleArguments < Thor + desc "say MSG [MSG]", "print multiple messages" + def say(*args) + puts args + end + + default_task :say +end + +class PluginWithDefaultTaskAndDeclaredArgument < Thor + desc "say MSG [MSG]", "print multiple messages" + argument :msg + def say + puts msg + end + + default_task :say +end + BoringVendorProvidedCLI.register( ExcitingPluginCLI, "exciting", @@ -79,6 +107,24 @@ def animal "zoo [-w animal]", "Shows a provided animal or just zebra") +BoringVendorProvidedCLI.register( + PluginWithDefault, + 'say', + 'say message', + 'subcommands ftw') + +BoringVendorProvidedCLI.register( + PluginWithDefaultMultipleArguments, + 'say_multiple', + 'say message', + 'subcommands ftw') + +BoringVendorProvidedCLI.register( + PluginWithDefaultTaskAndDeclaredArgument, + 'say_argument', + 'say message', + 'subcommands ftw') + describe ".register-ing a Thor subclass" do it "registers the plugin as a subcommand" do fireworks_output = capture(:stdout) { BoringVendorProvidedCLI.start(%w[exciting fireworks]) } @@ -90,6 +136,22 @@ def animal expect(help_output).to include('do exciting things') end + it "invokes the default task correctly" do + output = capture(:stdout) { BoringVendorProvidedCLI.start(%w[say hello]) } + expect(output).to include("hello") + end + + it "invokes the default task correctly with multiple args" do + output = capture(:stdout) { BoringVendorProvidedCLI.start(%w[say_multiple hello adam]) } + expect(output).to include("hello") + expect(output).to include("adam") + end + + it "invokes the default task correctly with a declared argument" do + output = capture(:stdout) { BoringVendorProvidedCLI.start(%w[say_argument hello]) } + expect(output).to include("hello") + end + context "when $thor_runner is false" do it "includes the plugin's subcommand name in subcommand's help" do begin