Passing Argument To Rake Dependency - ruby

I understand how to pass an argument to rake task, but I can't work out how to pass an argument to a dependent task.
I have a task that just runs a list of dependent tasks:
task foo [:alpha, :bravo, :charlie, :delta]
I need to add a task called :omega:
task foo [:omega, :alpha, :bravo, :charlie, :delta]
But I need to pass a parameter to it. I don't want to pass the parameter in to :foo - I just want to hardcode the parameter into the dependent task. I want to do this (I know it isn't valid):
task foo [:omega('Some Param'), :alpha, :bravo, :charlie, :delta]
How do I pass in the parameter?

Rake will run all the "dependent" tasks before running your task...so I doubt you will be able to achieve the above
Can you explain further what you are trying to achieve?
Jusy as an example if the task is run from the command line you may be able to do the following
rake foo omegaVar
In you code
def confirmDestruction
print "Are you sure you want to do that?\n"
answer=gets()
if answer == 'y'
return true
else
exit 0
end
end
task :omega do
$conrimedDestruction = confirmDestruction if $conrimedDestruction.nil?
if $conrimedDestruction
print "${$omegaVar}\n"
else
print "non destructive functionality\n"
end
end
task :alpha do
if $conrimedDestruction
print "${$omegaVar}\n"
else
print "non destructive functionality\n"
end
end
task :foo => [:omega, :alpha, :bravo, :charlie, :delta]

For better or worse, that is not possible. The available options are:
task :t do
task :t, :a, :b, ... do
task :t, [:a, :b, ...] do
if you don't specify dependencies. And:
task :t => [:d1, d2, ...] do
task :t, [:a, :b, ...] => [:d1, d2, ...] do
if you do.
Unless you want to invoke it manually:
task :a, :p do |task, args|
puts "p: #{args[:p]}"
end
task :b do
Rake::Task[:a].invoke(1)
end
$ rake b
p: 1

Related

Unified way in Rake to work with arguments passed by #invoke and #execute

I expected a Task to get arguments the same way, transparent to the caller methods #invoke or #execute:
desc "Add task"
task :add do |t, args|
puts args.class
puts "Add"
end
desc "Sub task"
task :sub do |t, args|
puts args.class
puts "Sub"
end
desc "all"
task :all do
Rake::Task['add'].execute("arg1") # cannot set multiple arguments, will fail with "wrong number of arguments"
Rake::Task['sub'].invoke("arg1", "arg2")
end
The result was:
ยป rake all
String
Add
Rake::TaskArguments
Sub
Upon checking the Rake source code it is clear these implementations are different.
Is there an unified way to manage arguments regardless where they come from? (command line, #invoke or #execution?). I use OptParse for the command line arguments so I have two ugly workarounds in my code now.
i assume that in case of execute if number of arguments > 1 then you want to execute with an argument is an array collect all those arguments, otherwise execute will execute with nil or the only input argument (a String, for example). So the way you call execute will match with the way you call invoke and execute still do the same as origin.
you could create a wrapper (alias) for Rake::Task#execute and handle the input arguments as below
# Rakefile
Rake::Task.alias_method :old_execute, :execute
Rake::Task.define_method("execute") do |*args|
if args&.size > 1
old_execute(args)
else
old_execute(args&.first)
end
end
# ...
Rake::Task['add'].execute # Rake::TaskArguments
Rake::Task['add'].execute("arg1") # String
Rake::Task['add'].execute("arg1", "arg2") # Array

Thor CLI: Setting a custom order of commands in help output

The thor gem seems to always order the defined commands alphabetically when printing its help output. Example:
#!/usr/bin/env ruby
require "thor"
class MyCLI < Thor
desc "z", "this should go first"
def z; end
desc "a", "this should go second"
def a; end
end
MyCLI.start(ARGV)
Saving this script as thor-test and calling it without arguments gives this output:
Commands:
thor-test a # this should go second
thor-test help [COMMAND] # Describe available commands or one specific command
thor-test z # this should go first
Question: How can I tell Thor to order the entries differently?
Seems as if Thor doesn't offer a configuration option for this. So I'll settle on some monkey-patching for now. aristotll's answer pointed me to the right place in Thor's source code.
But instead of hacking the <=> method, I decided to change the implementation of the help method. This seems still cleaner to me and has the advantage that I can further influence the behavior of the help output:
#!/usr/bin/env ruby
require "thor"
class MyCLI < Thor
class << self
def help(shell, subcommand = false)
list = printable_commands(true, subcommand)
Thor::Util.thor_classes_in(self).each do |klass|
list += klass.printable_commands(false)
end
# Remove this line to disable alphabetical sorting
# list.sort! { |a, b| a[0] <=> b[0] }
# Add this line to remove the help-command itself from the output
list.reject! {|l| l[0].split[1] == 'help'}
if defined?(#package_name) && #package_name
shell.say "#{#package_name} commands:"
else
shell.say "Commands:"
end
shell.print_table(list, :indent => 2, :truncate => true)
shell.say
class_options_help(shell)
# Add this line if you want to print custom text at the end of your help output.
# (similar to how Rails does it)
shell.say 'All commands can be run with -h (or --help) for more information.'
end
end
desc "z", "this should go first"
def z; end
desc "a", "this should go second"
def a; end
end
MyCLI.start(ARGV)
From the help source code
list.sort! { |a, b| a[0] <=> b[0] }
It is sorted alphabetically as expected.
Of course evil monkey patch is always allowed, add the following code before MyCLI.
SCRIPT = File.basename $PROGRAM_NAME
class String
alias old_compare <=>
# #param [String] other_string
# #return [Fixnum]
def <=>(other_string)
# currently the command name is like `script_name+space+usage`
# a monkey patch to make z goes first
if other_string.start_with?(SCRIPT)
index = SCRIPT.size + 1
if other_string[index] == 'z'
return 1
elsif self[index] =='z'
return -1
end
end
old_compare other_string
end
end
The output:
Commands:
thor-test z # this should go first
thor-test a # this should go second
thor-test help [COMMAND] # Describe available commands or one specific command

RSpec thinks that block does not receive "call" message?

I would like to use RSpec to ensure that my enumerable class is compatible with Ruby's visitor pattern:
# foo.rb
class Foo
def initialize(enum)
#enum = enum
end
include Enumerable
def each(&block)
#enum.each(&block)
end
end
Here is my rspec file:
# spec/foo_spec.rb
require 'rspec'
require './foo.rb'
describe Foo do
let(:items) { [1, 2, 3] }
describe '#each' do
it 'calls the given block each time' do
block = proc { |x| x }
block.should_receive(:call).exactly(items.size).times
Foo.new(items).each(&block)
end
end
end
But surprisingly, my examples fail when run (with rspec v2.14.5):
# $ bundle exec rspec
Failures:
1) Foo#each calls the given block each time
Failure/Error: block.should_receive(:call).exactly(items.size).times
(#<Proc:0x007fbabbdf3f90#/private/tmp/rspec-mystery/spec/foo_spec.rb:8>).call(any args)
expected: 3 times with any arguments
received: 0 times with any arguments
# ./spec/foo_spec.rb:12:in `block (3 levels) in <top (required)>'
Finished in 0.00082 seconds
1 example, 1 failure
Failed examples:
rspec ./spec/foo_spec.rb:11 # Foo#each calls the given block each time
Even more surprising, the class itself behaves exactly as I expect when used via ruby/irb:
# $ irb -r ./foo.rb
1.9.3-p125 :002 > f = Foo.new [1, 2, 3]
=> #<Foo:0x007ffda4059f70 #enum=[1, 2, 3]>
1.9.3-p125 :003 > f.each
=> #<Enumerator: [1, 2, 3]:each>
1.9.3-p125 :004 > block = proc { |x| puts "OK: #{x}" }
=> #<Proc:0x007ffda483fcd0#(irb):4>
1.9.3-p125 :005 > f.each &block
OK: 1
OK: 2
OK: 3
=> [1, 2, 3]
Why doesn't RSpec notice that the "block" does in fact receive the "call" message three times?
Why doesn't RSpec notice that the "block" does in fact receive the "call" message three times?
Because, AFAICT, on MRI, it doesn't.
#each isn't provided by Enumerable, only by the classes that implement it, and in your test, you're using an Array.
Here's the source code (in C) from Array#each:
VALUE rb_ary_each(VALUE array)
{
long i;
volatile VALUE ary = array;
RETURN_SIZED_ENUMERATOR(ary, 0, 0, rb_ary_length);
for (i=0; i<RARRAY_LEN(ary); i++) {
rb_yield(RARRAY_PTR(ary)[i]);
}
return ary;
}
From this, it looks like Array#each yields to the block rather than calling it explicitly.
UPDATE:
Your code & test fails on Rubinius & JRuby as well, so it looks like their standard libraries don't use call here either. As #mechanicalfish points out, you really only need to test that the iterator goes over the collection the correct number of times.
Blocks are not turned into Procs before being yielded to in MRI, per Matz's comment at https://www.ruby-forum.com/topic/71221, so it's understandable that they don't receive a :call as part of the yield process. Further, I don't believe there is any way to set expectations on a block, per se, as there is no way to reference a block as an object in the Ruby language.
You can, however, set expectations on Procs that they will receive the :call message and things will behave as you would expect.

Named parameters in Ruby 2

I don't understand completely how named parameters in Ruby 2.0 work.
def test(var1, var2, var3)
puts "#{var1} #{var2} #{var3}"
end
test(var3:"var3-new", var1: 1111, var2: 2222) #wrong number of arguments (1 for 3) (ArgumentError)
it's treated like a hash. And it's very funny because to use named parameters in Ruby 2.0 I must set default values for them:
def test(var1: "var1", var2: "var2", var3: "var3")
puts "#{var1} #{var2} #{var3}"
end
test(var3:"var3-new", var1: 1111, var2: 2222) # ok => 1111 2222 var3-new
which very similar to the behaviour which Ruby had before with default parameters' values:
def test(var1="var1", var2="var2", var3="var3")
puts "#{var1} #{var2} #{var3}"
end
test(var3:"var3-new", var1: 1111, var2: 2222) # ok but ... {:var3=>"var3-new", :var1=>1111, :var2=>2222} var2 var3
I know why is that happening and almost how it works.
But I'm just curious, must I use default values for parameters if I use named parameters?
And, can anybody tell me what's the difference between these two then?
def test1(var1="default value123")
#.......
end
def test1(var1:"default value123")
#.......
end
I think that the answer to your updated question can be explained with explicit examples. In the example below you have optional parameters in an explicit order:
def show_name_and_address(name="Someone", address="Somewhere")
puts "#{name}, #{address}"
end
show_name_and_address
#=> 'Someone, Somewhere'
show_name_and_address('Andy')
#=> 'Andy, Somewhere'
The named parameter approach is different. It still allows you to provide defaults but it allows the caller to determine which, if any, of the parameters to provide:
def show_name_and_address(name: "Someone", address: "Somewhere")
puts "#{name}, #{address}"
end
show_name_and_address
#=> 'Someone, Somewhere'
show_name_and_address(name: 'Andy')
#=> 'Andy, Somewhere'
show_name_and_address(address: 'USA')
#=> 'Someone, USA'
While it's true that the two approaches are similar when provided with no parameters, they differ when the user provides parameters to the method. With named parameters the caller can specify which parameter is being provided. Specifically, the last example (providing only the address) is not quite achievable in the first example; you can get similar results ONLY by supplying BOTH parameters to the method. This makes the named parameters approach much more flexible.
The last example you posted is misleading. I disagree that the behavior is similar to the one before. The last example passes the argument hash in as the first optional parameter, which is a different thing!
If you do not want to have a default value, you can use nil.
If you want to read a good writeup, see "Ruby 2 Keyword Arguments".
As of Ruby 2.1.0, you no longer have to set default values for named parameters. If you omit the default value for a parameter, the caller will be required to provide it.
def concatenate(val1: 'default', val2:)
"#{val1} #{val2}"
end
concatenate(val2: 'argument')
#=> "default argument"
concatenate(val1: 'change')
#=> ArgumentError: missing keyword: val2
Given:
def test1(var1="default value123")
var1
end
def test2(var1:"default value123")
var1
end
They'll behave the same way when not passed an argument:
test1
#=> "default value123"
test2
#=> "default value123"
But they'll behave much differently when an argument is passed:
test1("something else")
#=> "something else"
test2("something else")
#=> ArgumentError: wrong number of arguments (1 for 0)
test1(var1: "something else")
#=> {:var1=>"something else"}
test2(var1: "something else")
#=> "something else"
I agree with you that it's weird to require default values as the price for using named parameters, and evidently the Ruby maintainers agree with us! Ruby 2.1 will drop the default value requirement as of 2.1.0-preview1.
This is present in all the other answers, but I want to extract this essence.
There are four kinds of parameter:
Required
Optional
Positional
def PR(a)
def PO(a=1)
Keyword
def KR(a:)
def KO(a:1)
When defining a function, positional arguments are specified before keyword arguments, and required arguments before optional ones.
irb(main):006:0> def argtest(a,b=2,c:,d:4)
irb(main):007:1> p [a,b,c,d]
irb(main):008:1> end
=> :argtest
irb(main):009:0> argtest(1,c: 3)
=> [1, 2, 3, 4]
irb(main):010:0> argtest(1,20,c: 3,d: 40)
=> [1, 20, 3, 40]
EDIT: the required keyword argument (without a default value) is new as of Ruby 2.1.0, as mentioned by others.
Leaving this here because it helped me a lot.
Example
Suppose you have this:
def foo(thing, to_print)
if to_print
puts thing
end
end
# this works
foo("hi", true)
# hi
# => nil
so you try adding the argument names, like so:
foo(thing: "hi", to_print: true)
# foo(thing: "hi", to_print: true)
# ArgumentError: wrong number of arguments (given 1, expected 2)
# from (pry):42:in `foo'
but unfortunately it errors.
Solution
Just add a : to the end of each argument:
def foo2(thing:, to_print:)
if to_print
puts thing
end
end
foo2(thing: "hi", to_print: true)
# hi
# => nil
And it works!
According to "Ruby 2.0.0 by Example" you must have defaults:
In Ruby 2.0.0, keyword arguments must have defaults, or else must be captured by **extra at the end.
def test(a = 1, b: 2, c: 3)
p [a,b,c]
end
test #=> [1,2,3]
test 10 #=> [10,2,3]
test c:30 #=> [1,2,30] <- this is where named parameters become handy.
You can define the default value and the name of the parameter and then call the method the way you would call it if you had hash-based "named" parameters but without the need to define defaults in your method.
You would need this in your method for each "named parameter" if you were using a hash.
b = options_hash[:b] || 2
as in:
def test(a = 1, options_hash)
b = options_hash[:b] || 2
c = options_hash[:c] || 3
p [a,b,c]
end
You can define named parameters like
def test(var1: var1, var2: var2, var3: var3)
puts "#{var1} #{var2} #{var3}"
end
If you don't pass one of the parameters, then Ruby will complain about an undefined local variable or method.

How do I execute Rake tasks with arguments multiple times?

It's not possible to invoke the same rake task from within a loop more than once. But, I want to be able to call rake first and loop through an array and invoke second on each iteration with different arguments. Since invoke only gets executed the first time around, I tried to use execute, but Rake::Task#execute doesn't use the splat (*) operator and only takes a single argument.
desc "first task"
task :first do
other_arg = "bar"
[1,2,3,4].each_with_index do |n,i|
if i == 0
Rake::Task["foo:second"].invoke(n,other_arg)
else
# this doesn't work
Rake::Task["foo:second"].execute(n,other_arg)
end
end
end
task :second, [:first_arg, :second_arg] => :prerequisite_task do |t,args|
puts args[:first_arg]
puts args[:second_arg]
# ...
end
One hack around it is to put the arguments to execute into an array and in second examine the structure of args, but that seems, well, hackish. Is there another (better?) way to accomplish what I'd like to do?
You can use Rake::Task#reenable to allow it to be invoked again.
desc "first task"
task :first do
other_arg = "bar"
[1,2,3,4].each_with_index do |n,i|
if i == 0
Rake::Task["second"].invoke(n,other_arg)
else
# this does work
Rake::Task["second"].reenable
Rake::Task["second"].invoke(n,other_arg)
end
end
end
task :second, [:first_arg, :second_arg] do |t,args|
puts args[:first_arg]
puts args[:second_arg]
# ...
end
$ rake first
1
bar
2
bar
3
bar
4
bar
The execute function asks for a Rake::TaskArguments as a parameter, this is why it only accepts one argument.
You could use
stuff_args = {:match => "HELLO", :freq => '100' }
Rake::Task["stuff:sample"].execute(Rake::TaskArguments.new(stuff_args.keys, stuff_args.values))
However there is another difference between invoke and execute, execute doesn't run the :prerequisite_task when invoke does this first, so invoke and reenable or execute doesn't have exactly the same meaning.
FWIW this might help someone so I'll post it.
I wanted to be able to run one command from the CLI to run one Rake task multiple times (each time with new arguments, but that's not important).
Example:
rake my_task[x] my_task[y] my_task[z]
However, since Rake sees all my_task as the same task regardless of the args, it will only invoke the first time my_task[x] and will not invoke my_task[y] and my_task[z].
Using the Rake::Task#reenable method as mentioned in the other answers, I wrote a reenable Rake task which you can position to run after a task to allow it to run again.
Result:
rake my_task[x] reenable[my_task] my_task[y] reenable[my_task] my_task[z]
I wouldn't say this is ideal but it works for my case.
reenable Rake task source:
task :reenable, [:taskname] do |_task, args|
Rake::Task[args[:taskname]].reenable
Rake::Task[:reenable].reenable
end
This worked for me, it's quite easy to understand you just need to loop you bash command.
task :taskname, [:loop] do |t, args|
$i = 0
$num = args.loop.to_i
while $i < $num do
sh 'your bash command''
$i +=1
end
end

Resources