Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to use named arguments when invoking a task #370

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
55 changes: 46 additions & 9 deletions lib/rake/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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*(?<arg>(?:[^\\,]|\\.)*?)\s*(?:,\s*(?<remaining_args>.*))?$/ =~ 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*
(?<arg_name>(?:[^\\:]|\\.)*?)
\s*:\s*
(?<arg_value>(?:[^\\,]|\\.)*?)
\s*
(?:,\s*(?<remaining_args>.*))?
$/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:
Expand Down
5 changes: 5 additions & 0 deletions lib/rake/task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 34 additions & 0 deletions test/test_rake_task_argument_parsing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions test/test_rake_task_with_arguments.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down