lib/temple/mixins.rb in temple-0.1.8 vs lib/temple/mixins.rb in temple-0.2.0

- old
+ new

@@ -1,33 +1,164 @@ 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 compile(exp) - compile!(exp) + def call(exp) + compile(exp) end - def compile!(exp) + def compile(exp) type, *args = exp if respond_to?("on_#{type}") send("on_#{type}", *args) else exp end end - def on_multi(*exps) - [:multi, *exps.map {|exp| compile!(exp) }] - end - - def on_capture(name, exp) - [:capture, name, compile!(exp)] - 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}") @@ -39,32 +170,52 @@ 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 ClassMethods } + base.class_eval { extend DefaultOptions } end attr_reader :options def initialize(options = {}) - @options = self.class.default_options.merge(options) + @options = Utils::ImmutableHash.new(options, self.class.default_options) end + end - module ClassMethods - def set_default_options(opts) - default_options.merge!(opts) - end + module Template + include DefaultOptions - def default_options - @default_options ||= if superclass.respond_to?(:default_options) - Hash.new {|hash, key| superclass.default_options[key] } - else - {} - end + 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