Skip to content

Instantly share code, notes, and snippets.

@gabrielg
Created October 7, 2009 19:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save gabrielg/204377 to your computer and use it in GitHub Desktop.
Save gabrielg/204377 to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby
require 'rubygems'
require 'parse_tree'
require 'unified_ruby'
require 'ruby2ruby'
module ArgumentCoercion
def coerce_def(method_name, coercions)
parser = ParseTree.new(false)
unifier = Unifier.new
old_sexp = parser.parse_tree_for_method(self, method_name)
method_sexp = unifier.process(old_sexp)
splat_args = method_sexp[2][1..-1].collect do |n|
next(nil) unless n.is_a?(Symbol) && n.to_s[0] == ?*
n.to_s[1..-1]
end.compact
coercion_calls = coercions.inject([[],[]]) do |parallel,(arg_name,coercion)|
parallel[0] << arg_name.to_s
if splat_args.include?(arg_name.to_s)
parallel[1] << "#{arg_name}.collect {|a| a.#{coercion}}"
else
parallel[1] << "#{arg_name}.#{coercion}"
end
parallel
end
coercion_code = "#{coercion_calls[0].join(", ")} = #{coercion_calls[1].join(", ")}"
coerced_code = Ruby2Ruby.new.process(method_sexp).sub("\n", "\n #{coercion_code}\n")
module_eval(coerced_code)
end
end
class Module
include ArgumentCoercion
def singleton_method_added(name)
Thread.current['last_method_added'] = {:name => name.to_sym, :scope => class << self; self; end}
end
def method_added(name)
Thread.current['last_method_added'] = {:name => name.to_sym, :scope => self}
end
end
class NilClass
def coerce(coercions)
details = Thread.current['last_method_added']
details[:scope].coerce_def(details[:name], coercions)
end
end
if $PROGRAM_NAME == __FILE__
require 'riot'
class CoercedClass
def single_def(foo, bar)
return foo, bar
end.coerce(:foo => :to_sym)
def standard_def(foo, bar, baz)
return foo, bar, baz
end.coerce(:foo => :to_i, :bar => :to_s, :baz => :to_sym)
def splat_def(foo, *bar)
return foo, bar
end.coerce(:foo => :to_f, :bar => :to_sym)
def defaults_def(foo = 3)
return foo
end.coerce(:foo => :to_s)
class << self
def class_def(foo)
return foo
end.coerce(:foo => :to_sym)
end # self
end
module CoercedModule
def regular_def(snafu)
return snafu
end.coerce(:snafu => :to_sym)
class << self
def module_def(snafu)
return snafu
end.coerce(:snafu => :to_sym)
end
def self.module_def_outside_reopening_self(snafu)
return snafu
end.coerce(:snafu => :to_f)
end
context "coerce_def" do
context "given an instance of a class" do
setup do
CoercedClass.new
end
should "return a symbol and a string for single_def" do
topic.single_def("foo", "bar")
end.equals([:foo, "bar"])
should "return an integer, a string, and a symbol for standard_def" do
topic.standard_def("47", :what, "test")
end.equals([47, "what", :test])
should "return a float and array of symbols for splat_def" do
topic.splat_def("4.7", "foo", "bar", "baz")
end.equals([4.7, [:foo, :bar, :baz]])
should "also coerce defaults for defaults_def" do
topic.defaults_def
end.equals("3")
end # given an instance of a class
context "given a class" do
should "allow coercions for class methods like class_def" do
CoercedClass.class_def("ohman")
end.equals(:ohman)
end # given a class
context "given an object extended with a module" do
setup do
Object.new.extend(CoercedModule)
end
should "allow coercions for the module methods" do
topic.regular_def("what")
end.equals(:what)
end # given an object extended with a module
context "given a module" do
should "allow coercions for module methods like module_def" do
CoercedModule.module_def("huh")
end.equals(:huh)
should "allow coercions for module methods like module_def_outside_reopening_self" do
CoercedModule.module_def_outside_reopening_self("4.7")
end.equals(4.7)
end # given a module
end
Riot.report
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment