From: Sean O'Halpin Date: 2011-11-07T23:14:44+09:00 Subject: Re: Tricky DSL, how to do it? On Sun, Nov 6, 2011 at 10:35 PM, Intransition wrote: > I'd want to write a DSL such that a surface method_missing catches undefined > methods and records the blocks that they define e.g. >   foo do >     puts "foo" >   end > So I would end up with: >   { :foo=># } > Presently I have something like this: > >   # >   class Evaluator < BasicObject >     def initialize(&block) >       @__config__ = {} >       instance_eval(&block) if block >     end >     def __config__ >       @__config__ >     end >     def method_missing(sym, *args, &block) >       @__config__[sym] = block >     end >   end > However when I call on a block I want it to evaluate as if in the defining > context (in this case toplevel), not inside the "DSL" class that evaluated > via method_missing. >   e = Evaluator.new do >     foo do >       puts "foo" >     end >   end > >   e.__config__[:foo].call > Instead of what I want, I get a method missing error for #puts. > Any ideas? You could capture the calling self by getting it from the binding of the block and hide the evaluating call behind your own #eval method: class Evaluator < BasicObject def initialize(&block) @__config__ = {} if block @context = block.binding.eval("self") instance_eval(&block) end end def __config__ @__config__ end def method_missing(sym, *args, &block) @__config__[sym] = block end def eval(sym, *args) @context.instance_exec(*args, &@__config__[sym]) end end e = Evaluator.new do foo do puts "foo" end bar do |txt| puts "txt = #{txt}" end end e.eval(:foo) # => "foo" e.eval(:bar, "hello") # => "txt = hello" Regards, Sean