1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
|
# frozen_string_literal: true
module Temple
module Mixins
# @api private
module EngineDSL
def chain_modified!
end
def append(*args, &block)
chain << chain_element(args, block)
chain_modified!
end
def prepend(*args, &block)
chain.unshift(chain_element(args, block))
chain_modified!
end
def remove(name)
name = chain_name(name)
raise "#{name} not found" unless chain.reject! {|i| name === i.first }
chain_modified!
end
alias use append
def before(name, *args, &block)
name = chain_name(name)
e = chain_element(args, block)
chain.map! {|f| name === f.first ? [e, f] : [f] }.flatten!(1)
raise "#{name} not found" unless chain.include?(e)
chain_modified!
end
def after(name, *args, &block)
name = chain_name(name)
e = chain_element(args, block)
chain.map! {|f| name === f.first ? [f, e] : [f] }.flatten!(1)
raise "#{name} not found" unless chain.include?(e)
chain_modified!
end
def replace(name, *args, &block)
name = chain_name(name)
e = chain_element(args, block)
chain.map! {|f| name === f.first ? e : f }
raise "#{name} not found" unless chain.include?(e)
chain_modified!
end
# Shortcuts to access namespaces
{ filter: Temple::Filters,
generator: Temple::Generators,
html: Temple::HTML }.each do |method, mod|
define_method(method) do |name, *options|
use(name, mod.const_get(name), *options)
end
end
private
def chain_name(name)
case name
when Class
name.name.to_sym
when Symbol, String
name.to_sym
when Regexp
name
else
raise(ArgumentError, 'Name argument must be Class, Symbol, String or Regexp')
end
end
def chain_class_constructor(filter, local_options)
define_options(filter.options.valid_keys) if respond_to?(:define_options) && filter.respond_to?(:options)
proc do |engine|
opts = {}.update(engine.options)
opts.delete_if {|k,v| !filter.options.valid_key?(k) } if filter.respond_to?(:options)
opts.update(local_options) if local_options
filter.new(opts)
end
end
def chain_proc_constructor(name, filter)
raise(ArgumentError, 'Proc or blocks must have arity 0 or 1') if filter.arity > 1
method_name = "FILTER #{name}"
c = Class === self ? self : singleton_class
filter = c.class_eval { define_method(method_name, &filter); instance_method(method_name) }
proc do |engine|
if filter.arity == 1
# the proc takes one argument, e.g. use(:Filter) {|exp| exp }
filter.bind(engine)
else
f = filter.bind(engine).call
if f.respond_to? :call
# the proc returns a callable object, e.g. use(:Filter) { Filter.new }
f
else
raise(ArgumentError, 'Proc or blocks must return a Callable or a Class') unless f.respond_to? :new
# the proc returns a class, e.g. use(:Filter) { Filter }
f.new(f.respond_to?(:options) ? engine.options.to_hash.select {|k,v| f.options.valid_key?(k) } : engine.options)
end
end
end
end
def chain_element(args, block)
name = args.shift
if Class === name
filter = name
name = filter.name.to_sym
else
raise(ArgumentError, 'Name argument must be Class or Symbol') unless Symbol === name
end
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?
[name, chain_proc_constructor(name, filter)]
when Class
# Class argument (e.g Filter class)
# The options are passed to the classes constructor.
raise(ArgumentError, 'Too many arguments') if args.size > 1
[name, chain_class_constructor(filter, args.first)]
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, 'Too many arguments') unless args.empty?
raise(ArgumentError, 'Class or callable argument is required') unless filter.respond_to?(:call)
[name, proc { filter }]
end
end
end
end
end
|