1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
require 'backports/tools/alias_method_chain'
module Backports
# Metaprogramming utility to make block optional.
# Tests first if block is already optional when given options
def self.make_block_optional(mod, *methods)
mod = class << mod; self; end unless mod.is_a? Module
options = methods.last.is_a?(Hash) ? methods.pop : {}
methods.each do |selector|
unless mod.method_defined? selector
warn "#{mod}##{selector} is not defined, so block can't be made optional"
next
end
unless options[:force]
# Check if needed
test_on = options.fetch(:test_on)
result = begin
test_on.send(selector, *options.fetch(:arg, []))
rescue LocalJumpError
false
end
next if result.class.name =~ /Enumerator/
end
require 'enumerator'
arity = mod.instance_method(selector).arity
last_arg = []
if arity < 0
last_arg = ["*rest"]
arity = -1-arity
end
arg_sequence = ((0...arity).map{|i| "arg_#{i}"} + last_arg + ["&block"]).join(", ")
alias_method_chain(mod, selector, :optional_block) do |aliased_target, punctuation|
mod.module_eval <<-end_eval, __FILE__, __LINE__ + 1
def #{aliased_target}_with_optional_block#{punctuation}(#{arg_sequence})
return to_enum(:#{aliased_target}_without_optional_block#{punctuation}, #{arg_sequence}) unless block_given?
#{aliased_target}_without_optional_block#{punctuation}(#{arg_sequence})
end
end_eval
end
end
end
end
|