moduleTemplemoduleMixins# @api privatemoduleCoreDispatcherdefon_multi(*exps)multi=[:multi]exps.each{|exp|multi<<compile(exp)}multienddefon_capture(name,exp)[:capture,name,compile(exp)]endend# @api privatemoduleEscapeDispatcherdefon_escape(flag,exp)[:escape,flag,compile(exp)]endend# @api privatemoduleControlFlowDispatcherdefon_if(condition,*cases)[:if,condition,*cases.compact.map{|e|compile(e)}]enddefon_case(arg,*cases)[:case,arg,*cases.map{|condition,exp|[condition,compile(exp)]}]enddefon_block(code,content)[:block,code,compile(content)]enddefon_cond(*cases)[:cond,*cases.map{|condition,exp|[condition,compile(exp)]}]endend# @api privatemoduleCompiledDispatcherdefcall(exp)compile(exp)enddefcompile(exp)dispatcher(exp)endprivatedefdispatcher(exp)replace_dispatcher(exp)enddefreplace_dispatcher(exp)tree=DispatchNode.newdispatched_methods.eachdo|method|method.split('_'.freeze)[1..-1].inject(tree){|node,type|node[type.to_sym]}.method=methodendself.class.class_eval<<-RUBY,__FILE__,__LINE__+1
def dispatcher(exp)
return replace_dispatcher(exp) if self.class != #{self.class}#{tree.compile.gsub("\n","\n ")}
end
RUBYdispatcher(exp)enddefdispatched_methodsre=/^on(_[a-zA-Z0-9]+)*$/self.methods.map(&:to_s).select(&re.method(:=~))end# @api privateclassDispatchNode<Hashattr_accessor:methoddefinitializesuper{|hsh,key|hsh[key]=DispatchNode.new}@method=nilenddefcompile(level=0,call_parent=nil)call_method=method?(level==0?"#{method}(*exp)":"#{method}(*exp[#{level}..-1])"):call_parentifempty?raise'Invalid dispatcher node'unlessmethodcall_methodelsecode=String.newcode<<"case(exp[#{level}])\n"eachdo|key,child|code<<"when #{key.inspect}\n "<<child.compile(level+1,call_method).gsub("\n".freeze,"\n ".freeze)<<"\n".freezeendcode<<"else\n "<<(call_method||'exp')<<"\nend"endendendend# @api public## Implements a compatible call-method# based on the including classe's methods.## It uses every method starting with# "on" and uses the rest of the method# name as prefix of the expression it# will receive. So, if a dispatcher# has a method named "on_x", this method# will be called with arg0,..,argN# whenever an expression like [:x, arg0,..,argN ]# is encountered.## This works with longer prefixes, too.# For example a method named "on_y_z"# will be called whenever an expression# like [:y, :z, .. ] is found. Furthermore,# if additionally a method named "on_y"# is present, it will be called when an# expression starts with :y but then does# not contain with :z. This way a# dispatcher can implement namespaces.## @note# Processing does not reach into unknown# expression types by default.## @example# class MyAwesomeDispatch# include Temple::Mixins::Dispatcher# def on_awesome(thing) # keep awesome things# return [:awesome, thing]# end# def on_boring(thing) # make boring things awesome# return [:awesome, thing+" with bacon"]# end# def on(type,*args) # unknown stuff is boring too# return [:awesome, 'just bacon']# end# end# filter = MyAwesomeDispatch.new# # Boring things are converted:# filter.call([:boring, 'egg']) #=> [:awesome, 'egg with bacon']# # Unknown things too:# filter.call([:foo]) #=> [:awesome, 'just bacon']# # Known but not boring things won't be touched:# filter.call([:awesome, 'chuck norris']) #=>[:awesome, 'chuck norris']#moduleDispatcherincludeCompiledDispatcherincludeCoreDispatcherincludeEscapeDispatcherincludeControlFlowDispatcherendendend