lib/express_templates/expander.rb in express_templates-0.2.0 vs lib/express_templates/expander.rb in express_templates-0.2.2

- old
+ new

@@ -2,76 +2,91 @@ module ExpressTemplates class Expander cattr_accessor :module_search_space - attr_accessor :stack + attr_accessor :stack, :handlers, :locals - def self.expand(template, source) - expanded = new(template).expand(source) - compiled = expanded.map(&:compile) - return compiled.join("+") - end - - def initialize(template) + def initialize(template, handlers = {}, locals = {}) @template = template @stack = Stack.new + @handlers = handlers + @locals = locals end def expand(source=nil, &block) - if source - modified = source.gsub(/(\W)(yield)(\.*)?/, '\1 (stack << ExpressTemplates::Components::Yielder.new(\3))') - modified.gsub!(/(\W)(@\w+)(\W)?/, '\1 (stack << ExpressTemplates::Components::Wrapper.new("\2") )\3') + case + when block.nil? && source + modified = _wrap_instance_vars( _replace_yield_with_yielder(source) ) instance_eval(modified, @template.inspect) - stack.current + when block + instance_exec &block else - instance_eval &block - stack.current + raise ArgumentError end + stack.current end + def process_children!(parent, &block) + begin + stack.descend! + result = instance_exec &block + if stack.current.empty? && result.is_a?(String) + stack << result + end + parent.children += stack.current + stack.ascend! + end + stack.current + end + # define a "macro" method for a component # these methods accept args which are passed to the # initializer for the component - # blocks supplied are evaluated and any returned objects are - # added as children to the component + # blocks supplied are evaluated and children added to the "stack" + # are added as children to the component def self.register_macros_for(*components) components.each do |component| define_method(component.macro_name.to_sym) do |*args, &block| - stack << if block - begin - stack.descend! - block.call - # anything stored on stack.current or on stack.next is added as a child - # this is a bit problematic in the case where we would have - # blocks and helpers or locals mixed - component.new(*(args.push(*(stack.current)))) - ensure - stack.ascend! - end - else - component.new(*(args)) - end + new_component = component.new(*(args.push(self))) + process_children!(new_component, &block) unless block.nil? + stack << new_component end end end - @module_search_space = [ExpressTemplates::Components] + @module_search_space = [ExpressTemplates::Markup, ExpressTemplates::Components] @module_search_space.each do |mod| register_macros_for(* mod.constants.map { |const| [mod.to_s, const.to_s].join("::").constantize }. - select { |klass| klass.ancestors.include? (ExpressTemplates::Component) } + select { |klass| klass.ancestors.include? (ExpressTemplates::Markup::Tag) } ) end - def method_missing(name, *args) - stack.current << ExpressTemplates::Components::Wrapper.new(name.to_s, *args) + def method_missing(name, *args, &block) + return locals[name] if locals.keys.include?(name) + + if handlers.keys.include?(name) + stack << handlers[name].send(name, *args) + else + stack << ExpressTemplates::Markup::Wrapper.new(name.to_s, *args, &block) + end nil end + private + + def _replace_yield_with_yielder(source) + source.gsub(/(\W)(yield)(\([^\)]*\))?/, '\1 (stack << ExpressTemplates::Markup::Yielder.new\3)') + end + + def _wrap_instance_vars(source) + source.gsub(/(\W)(@\w+)(\W)?/, '\1 (stack << ExpressTemplates::Markup::Wrapper.new("\2") )\3') + end + class Stack def initialize @stack = [[]] @frame = 0 end @@ -80,10 +95,11 @@ @stack end def dump puts "Current frame: #{@frame}" - puts all.map(&:inspect).join("\n") + require 'pp' + pp all end def <<(child) current << child child \ No newline at end of file