diff --git a/lib/rake/application.rb b/lib/rake/application.rb index 9ac9b2130..80d63560d 100644 --- a/lib/rake/application.rb +++ b/lib/rake/application.rb @@ -161,25 +161,62 @@ def invoke_task(task_string) # :nodoc: end def parse_task_string(string) # :nodoc: - /^([^\[]+)(?:\[(.*)\])$/ =~ string.to_s + case string.to_s + when /^([^\[]+)(?:\[(.*)\])$/ # [one, two] + name = $1 + args = parse_positional_args_string($2) + when /^([^\{]+)(?:\{(.*)\})$/ # [key: value] + name = $1 + args = parse_named_args_string($2) + args = args.empty? ? [] : [args] + else + name = string + args = [] + end - name = $1 - remaining_args = $2 + return name, args + end - return string, [] unless name - return name, [] if remaining_args.empty? + def parse_positional_args_string(string) # :nodoc: + return [] if string.empty? args = [] + remaining_args = string begin - /\s*((?:[^\\,]|\\.)*?)\s*(?:,\s*(.*))?$/ =~ remaining_args + /\s*(?(?:[^\\,]|\\.)*?)\s*(?:,\s*(?.*))?$/ =~ remaining_args - remaining_args = $2 - args << $1.gsub(/\\(.)/, '\1') + args << arg.gsub(/\\(.)/, '\1') end while remaining_args - return name, args + args + end + private :parse_positional_args_string + + def parse_named_args_string(string) # :nodoc: + return {} if string.empty? + + args = {} + remaining_args = string + + begin + / + \s* + (?(?:[^\\:]|\\.)*?) + \s*:\s* + (?(?:[^\\,]|\\.)*?) + \s* + (?:,\s*(?.*))? + $/x =~ remaining_args + + arg_name = arg_name.gsub(/\\(.)/, '\1').to_sym + arg_value = arg_value.gsub(/\\(.)/, '\1') + args[arg_name] = arg_value + end while remaining_args + + args end + private :parse_named_args_string # Provide standard exception handling for the given block. def standard_exception_handling # :nodoc: diff --git a/lib/rake/task.rb b/lib/rake/task.rb index ec2c756e0..a8f30801f 100644 --- a/lib/rake/task.rb +++ b/lib/rake/task.rb @@ -184,6 +184,11 @@ def clear_args # Invoke the task if it is needed. Prerequisites are invoked first. def invoke(*args) + if args.first.is_a?(Hash) + opts = args.first + args = arg_names.map { |arg_name| opts[arg_name.to_sym] } + end + task_args = TaskArguments.new(arg_names, args) invoke_with_call_chain(task_args, InvocationChain::EMPTY) end diff --git a/test/test_rake_task_argument_parsing.rb b/test/test_rake_task_argument_parsing.rb index ed12ea0b4..d741307ac 100644 --- a/test/test_rake_task_argument_parsing.rb +++ b/test/test_rake_task_argument_parsing.rb @@ -18,42 +18,76 @@ def test_empty_args name, args = @app.parse_task_string("name[]") assert_equal "name", name assert_equal [], args + + name, args = @app.parse_task_string("name{}") + assert_equal "name", name + assert_equal [], args end def test_one_argument name, args = @app.parse_task_string("name[one]") assert_equal "name", name assert_equal ["one"], args + + name, args = @app.parse_task_string("name{one:1}") + assert_equal "name", name + assert_equal [{one: "1"}], args end def test_two_arguments name, args = @app.parse_task_string("name[one,two]") assert_equal "name", name assert_equal ["one", "two"], args + + name, args = @app.parse_task_string("name{one:1,two:2}") + assert_equal "name", name + assert_equal [{one: "1", two: "2"}], args end def test_can_handle_spaces_between_args name, args = @app.parse_task_string("name[one, two,\tthree , \tfour]") assert_equal "name", name assert_equal ["one", "two", "three", "four"], args + + name, args = @app.parse_task_string("name{one: 1, two:2,\tthree : 3, \tfour:4}") + assert_equal "name", name + assert_equal [{one: "1", two: "2", three: "3", four: "4"}], args end def test_can_handle_spaces_between_all_args name, args = @app.parse_task_string("name[ one , two ,\tthree , \tfour ]") assert_equal "name", name assert_equal ["one", "two", "three", "four"], args + + name, args = @app.parse_task_string("name{ one : 1, two:2 ,\tthree : 3 , \tfour: 4 }") + assert_equal "name", name + assert_equal [{one: "1", two: "2", three: "3", four: "4"}], args end def test_keeps_embedded_spaces name, args = @app.parse_task_string("name[a one ana, two]") assert_equal "name", name assert_equal ["a one ana", "two"], args + + name, args = @app.parse_task_string("name{a one ana: has value, two:2}") + assert_equal "name", name + assert_equal [{:"a one ana" => "has value", two: "2"}], args end def test_can_handle_commas_in_args name, args = @app.parse_task_string("name[one, two, three_a\\, three_b, four]") assert_equal "name", name assert_equal ["one", "two", "three_a, three_b", "four"], args + + name, args = @app.parse_task_string("name{one:1, two:2, three_a\\, three_b:3, four:4}") + assert_equal "name", name + assert_equal [{one: "1", two: "2", :"three_a, three_b" => "3", four: "4"}], args + end + + def test_can_handle_colons_in_named_args + name, args = @app.parse_task_string("name{one:1, two:2, three_a\\: three_b:3, four:4}") + assert_equal "name", name + assert_equal [{one: "1", two: "2", :"three_a: three_b" => "3", four: "4"}], args end def test_treat_blank_arg_as_empty_string diff --git a/test/test_rake_task_with_arguments.rb b/test/test_rake_task_with_arguments.rb index 36dfa2646..791c8cb9f 100644 --- a/test/test_rake_task_with_arguments.rb +++ b/test/test_rake_task_with_arguments.rb @@ -60,6 +60,18 @@ def test_tasks_can_access_arguments_as_hash t.invoke(1, 2, 3) end + def test_arguments_passed_as_hash + t = task :t, :a, :b do |tt, args| + assert_equal({ a: 1, b: 2 }, args.to_hash) + assert_equal 1, args[:a] + assert_equal 2, args[:b] + assert_equal 1, args.a + assert_equal 2, args.b + end + + t.invoke({a: 1, b: 2}) + end + def test_actions_of_various_arity_are_ok_with_args notes = [] t = task(:t, :x) do