diff --git a/lib/docile.rb b/lib/docile.rb index 5ad8bdd..8c526ec 100644 --- a/lib/docile.rb +++ b/lib/docile.rb @@ -1,9 +1,11 @@ require "docile/version" +require "docile/fallback_context_proxy" module Docile # Executes a block in the context of an object whose interface represents a DSL. # - # Docile.dsl_eval([]) do + # Example of using an Array as a DSL: + # Docile.dsl_eval [] do # push 1 # push 2 # pop @@ -16,7 +18,7 @@ module Docile # @return [Object] the given DSL object def dsl_eval(dsl, &block) block_context = eval("self", block.binding) - dsl.instance_eval(&block) + FallbackContextProxy.new(dsl, block_context).instance_eval(&block) dsl end module_function :dsl_eval diff --git a/lib/docile/fallback_context_proxy.rb b/lib/docile/fallback_context_proxy.rb new file mode 100644 index 0000000..4a87d25 --- /dev/null +++ b/lib/docile/fallback_context_proxy.rb @@ -0,0 +1,43 @@ +require 'set' + +module Docile + class FallbackContextProxy + BASIC_METHODS = Set[:==, :equal?, :"!", :"!=", :instance_eval, :object_id, :__send__, :__id__] + + instance_methods.each do |method| + unless BASIC_METHODS.include?(method.to_sym) + undef_method(method) + end + end + + def initialize(receiver, fallback) + @__receiver__ = receiver + @__fallback__ = fallback + end + + def id + @__receiver__.__send__(:id) + end + + # Special case due to `Kernel#sub`'s existence + def sub(*args, &block) + __proxy_method__(:sub, *args, &block) + end + + def method_missing(method, *args, &block) + __proxy_method__(method, *args, &block) + end + + def __proxy_method__(method, *args, &block) + begin + @__receiver__.__send__(method.to_sym, *args, &block) + rescue ::NoMethodError => e + begin + @__fallback__.__send__(method.to_sym, *args, &block) + rescue ::NoMethodError + raise(e) + end + end + end + end +end \ No newline at end of file diff --git a/spec/docile_spec.rb b/spec/docile_spec.rb index d917556..4e3a06e 100644 --- a/spec/docile_spec.rb +++ b/spec/docile_spec.rb @@ -30,8 +30,7 @@ describe Docile do end.should == [1, 3] end - context "methods" - + context "methods" do it "should find method of outer dsl in outer dsl scope" do outer { a.should == 'a' } end @@ -40,36 +39,44 @@ describe Docile do outer { inner { b.should == 'b' } } end - #it "should find method of outer dsl in inner dsl scope" do - # outer { inner { a.should == 'a' } } - #end - # - #it "should find method of block's context in outer dsl scope" do - # def c; 'c'; end - # outer { c.should == 'c' } - #end - # - #it "should find method of block's context in inner dsl scope" do - # def c; 'c'; end - # outer { inner { c.should == 'c' } } + it "should find method of outer dsl in inner dsl scope" do + outer { inner { a.should == 'a' } } + end + + it "should find method of block's context in outer dsl scope" do + def c; 'c'; end + outer { c.should == 'c' } + end + + it "should find method of block's context in inner dsl scope" do + def c; 'c'; end + outer { inner { c.should == 'c' } } + end + + it "should find method of outer dsl in preference to block context" do + def a; 'not a'; end + outer { a.should == 'a' } + end + end + + context "local variables" do + it "should find local variable from block context in outer dsl scope" do + foo = 'foo' + outer { foo.should == 'foo' } + end + + it "should find local variable from block definition in inner dsl scope" do + bar = 'bar' + outer { inner { bar.should == 'bar' } } + end + end + + context "instance variables" do + #it "should find instance variable from block definition in inner dsl scope" do + # @iv1 = 'iv1'; outer { inner { @iv1.should == 'iv1' } } #end end - # it "should find local variable from block context in outer dsl scope" do - # foo = 'foo' - # outer { foo.should == 'foo' } - # end - # - # - # - # it "should find local variable from block definition in inner dsl scope" do - # bar = 'bar' - # outer { inner { bar.should == 'bar' } } - # end - # - # it "should find instance variable from block definition in inner dsl scope" do - # @iv1 = 'iv1'; outer { inner { @iv1.should == 'iv1' } } - # end - #end + end end \ No newline at end of file