module Temple module Mixins module EngineDSL def append(*args, &block) chain << element(args, block) end def prepend(*args, &block) chain.unshift(element(args, block)) end def remove(name) found = false chain.reject! do |i| equal = i.first == name found = true if equal equal end raise "#{name} not found" unless found end alias use append def before(name, *args, &block) name = Class === name ? name.name.to_sym : name raise(ArgumentError, 'First argument must be Class or Symbol') unless Symbol === name e = element(args, block) found, i = false, 0 while i < chain.size if chain[i].first == name found = true chain.insert(i, e) i += 2 else i += 1 end end raise "#{name} not found" unless found end def after(name, *args, &block) name = Class === name ? name.name.to_sym : name raise(ArgumentError, 'First argument must be Class or Symbol') unless Symbol === name e = element(args, block) found, i = false, 0 while i < chain.size if chain[i].first == name found = true i += 1 chain.insert(i, e) end i += 1 end raise "#{name} not found" unless found end def replace(name, *args, &block) name = Class === name ? name.name.to_sym : name raise(ArgumentError, 'First argument must be Class or Symbol') unless Symbol === name e = element(args, block) found = false chain.each_with_index do |c, i| if c.first == name found = true chain[i] = e end end raise "#{name} not found" unless found end def filter(name, *options, &block) use(name, Temple::Filters.const_get(name), *options, &block) end def generator(name, *options, &block) use(name, Temple::Generators.const_get(name), *options, &block) end private def element(args, block) name = args.shift if Class === name filter = name name = filter.name.to_sym end raise(ArgumentError, 'First argument must be Class or Symbol') unless Symbol === name if block raise(ArgumentError, 'Class and block argument are not allowed at the same time') if filter filter = block end filter ||= args.shift case filter when Proc # Proc or block argument # The proc is converted to a method of the engine class. # The proc can then access the option hash of the engine. raise(ArgumentError, 'Too many arguments') unless args.empty? raise(ArgumentError, 'Proc or blocks must have arity 1') unless filter.arity == 1 method_name = "FILTER #{name}" if Class === self define_method(method_name, &filter) [name, instance_method(method_name)] else (class << self; self; end).class_eval { define_method(method_name, &filter) } [name, method(method_name)] end when Class # Class argument (e.g Filter class) # The options are passed to the classes constructor. local_options = Hash === args.last ? args.pop : nil raise(ArgumentError, 'Only symbols allowed in option filter') unless args.all? {|o| Symbol === o } [name, filter, args, local_options] else # Other callable argument (e.g. Object of class which implements #call or Method) # The callable has no access to the option hash of the engine. raise(ArgumentError, 'Class or callable argument is required') unless filter.respond_to?(:call) [name, filter] end end end module CoreDispatcher def on_multi(*exps) [:multi, *exps.map {|exp| compile(exp) }] end def on_capture(name, exp) [:capture, name, compile(exp)] end def on_escape(flag, exp) [:escape, flag, compile(exp)] end end module Dispatcher include CoreDispatcher def self.included(base) base.class_eval { extend ClassMethods } end def call(exp) compile(exp) end def compile(exp) type, *args = exp if respond_to?("on_#{type}") send("on_#{type}", *args) else exp end end module ClassMethods def temple_dispatch(*bases) bases.each do |base| class_eval %{def on_#{base}(type, *args) if respond_to?("on_" #{base.to_s.inspect} "_\#{type}") send("on_" #{base.to_s.inspect} "_\#{type}", *args) else [:#{base}, type, *args] end end} end end end end module DefaultOptions def set_default_options(options) default_options.update(options) end def default_options @default_options ||= Utils::MutableHash.new(superclass.respond_to?(:default_options) ? superclass.default_options : nil) end end module Options def self.included(base) base.class_eval { extend DefaultOptions } end attr_reader :options def initialize(options = {}) @options = Utils::ImmutableHash.new(options, self.class.default_options) end end module Template include DefaultOptions def engine(engine = nil) default_options[:engine] = engine if engine default_options[:engine] end def build_engine(*options) raise 'No engine configured' unless engine options << default_options engine.new(Utils::ImmutableHash.new(*options)) do |e| chain.each {|block| e.instance_eval(&block) } end end def chain(&block) chain = (default_options[:chain] ||= []) chain << block if block chain end end end end