temple 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.yardopts +2 -1
- data/CHANGES +63 -0
- data/EXPRESSIONS.md +250 -0
- data/README.md +24 -12
- data/lib/temple.rb +34 -18
- data/lib/temple/engine.rb +11 -7
- data/lib/temple/erb/engine.rb +5 -3
- data/lib/temple/erb/parser.rb +5 -2
- data/lib/temple/erb/template.rb +11 -0
- data/lib/temple/erb/trimming.rb +9 -1
- data/lib/temple/filter.rb +2 -0
- data/lib/temple/filters/control_flow.rb +43 -0
- data/lib/temple/filters/dynamic_inliner.rb +29 -32
- data/lib/temple/filters/eraser.rb +22 -0
- data/lib/temple/filters/escapable.rb +39 -0
- data/lib/temple/filters/multi_flattener.rb +4 -1
- data/lib/temple/filters/static_merger.rb +11 -10
- data/lib/temple/filters/validator.rb +15 -0
- data/lib/temple/generators.rb +41 -100
- data/lib/temple/grammar.rb +56 -0
- data/lib/temple/hash.rb +48 -0
- data/lib/temple/html/dispatcher.rb +10 -4
- data/lib/temple/html/fast.rb +50 -38
- data/lib/temple/html/filter.rb +8 -0
- data/lib/temple/html/pretty.rb +25 -14
- data/lib/temple/mixins/dispatcher.rb +103 -0
- data/lib/temple/{mixins.rb → mixins/engine_dsl.rb} +10 -95
- data/lib/temple/mixins/grammar_dsl.rb +166 -0
- data/lib/temple/mixins/options.rb +28 -0
- data/lib/temple/mixins/template.rb +25 -0
- data/lib/temple/templates.rb +2 -0
- data/lib/temple/utils.rb +11 -57
- data/lib/temple/version.rb +1 -1
- data/test/filters/test_control_flow.rb +92 -0
- data/test/filters/test_dynamic_inliner.rb +7 -7
- data/test/filters/test_eraser.rb +55 -0
- data/test/filters/{test_escape_html.rb → test_escapable.rb} +13 -6
- data/test/filters/test_multi_flattener.rb +1 -1
- data/test/filters/test_static_merger.rb +3 -3
- data/test/helper.rb +8 -0
- data/test/html/test_fast.rb +42 -57
- data/test/html/test_pretty.rb +10 -7
- data/test/mixins/test_dispatcher.rb +31 -0
- data/test/mixins/test_grammar_dsl.rb +86 -0
- data/test/test_engine.rb +73 -57
- data/test/test_erb.rb +0 -7
- data/test/test_filter.rb +26 -0
- data/test/test_generator.rb +57 -36
- data/test/test_grammar.rb +52 -0
- data/test/test_hash.rb +39 -0
- data/test/test_utils.rb +11 -38
- metadata +34 -10
- data/lib/temple/filters/debugger.rb +0 -26
- data/lib/temple/filters/escape_html.rb +0 -33
@@ -1,12 +1,18 @@
|
|
1
1
|
module Temple
|
2
2
|
module Mixins
|
3
|
+
# @api private
|
3
4
|
module EngineDSL
|
5
|
+
def chain_modified!
|
6
|
+
end
|
7
|
+
|
4
8
|
def append(*args, &block)
|
5
9
|
chain << element(args, block)
|
10
|
+
chain_modified!
|
6
11
|
end
|
7
12
|
|
8
13
|
def prepend(*args, &block)
|
9
14
|
chain.unshift(element(args, block))
|
15
|
+
chain_modified!
|
10
16
|
end
|
11
17
|
|
12
18
|
def remove(name)
|
@@ -17,6 +23,7 @@ module Temple
|
|
17
23
|
equal
|
18
24
|
end
|
19
25
|
raise "#{name} not found" unless found
|
26
|
+
chain_modified!
|
20
27
|
end
|
21
28
|
|
22
29
|
alias use append
|
@@ -36,6 +43,7 @@ module Temple
|
|
36
43
|
end
|
37
44
|
end
|
38
45
|
raise "#{name} not found" unless found
|
46
|
+
chain_modified!
|
39
47
|
end
|
40
48
|
|
41
49
|
def after(name, *args, &block)
|
@@ -52,6 +60,7 @@ module Temple
|
|
52
60
|
i += 1
|
53
61
|
end
|
54
62
|
raise "#{name} not found" unless found
|
63
|
+
chain_modified!
|
55
64
|
end
|
56
65
|
|
57
66
|
def replace(name, *args, &block)
|
@@ -66,6 +75,7 @@ module Temple
|
|
66
75
|
end
|
67
76
|
end
|
68
77
|
raise "#{name} not found" unless found
|
78
|
+
chain_modified!
|
69
79
|
end
|
70
80
|
|
71
81
|
def filter(name, *options, &block)
|
@@ -122,100 +132,5 @@ module Temple
|
|
122
132
|
end
|
123
133
|
end
|
124
134
|
end
|
125
|
-
|
126
|
-
module CoreDispatcher
|
127
|
-
def on_multi(*exps)
|
128
|
-
[:multi, *exps.map {|exp| compile(exp) }]
|
129
|
-
end
|
130
|
-
|
131
|
-
def on_capture(name, exp)
|
132
|
-
[:capture, name, compile(exp)]
|
133
|
-
end
|
134
|
-
|
135
|
-
def on_escape(flag, exp)
|
136
|
-
[:escape, flag, compile(exp)]
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
module Dispatcher
|
141
|
-
include CoreDispatcher
|
142
|
-
|
143
|
-
def self.included(base)
|
144
|
-
base.class_eval { extend ClassMethods }
|
145
|
-
end
|
146
|
-
|
147
|
-
def call(exp)
|
148
|
-
compile(exp)
|
149
|
-
end
|
150
|
-
|
151
|
-
def compile(exp)
|
152
|
-
type, *args = exp
|
153
|
-
if respond_to?("on_#{type}")
|
154
|
-
send("on_#{type}", *args)
|
155
|
-
else
|
156
|
-
exp
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
module ClassMethods
|
161
|
-
def temple_dispatch(*bases)
|
162
|
-
bases.each do |base|
|
163
|
-
class_eval %{def on_#{base}(type, *args)
|
164
|
-
if respond_to?("on_" #{base.to_s.inspect} "_\#{type}")
|
165
|
-
send("on_" #{base.to_s.inspect} "_\#{type}", *args)
|
166
|
-
else
|
167
|
-
[:#{base}, type, *args]
|
168
|
-
end
|
169
|
-
end}
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
module DefaultOptions
|
176
|
-
def set_default_options(options)
|
177
|
-
default_options.update(options)
|
178
|
-
end
|
179
|
-
|
180
|
-
def default_options
|
181
|
-
@default_options ||= Utils::MutableHash.new(superclass.respond_to?(:default_options) ?
|
182
|
-
superclass.default_options : nil)
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
module Options
|
187
|
-
def self.included(base)
|
188
|
-
base.class_eval { extend DefaultOptions }
|
189
|
-
end
|
190
|
-
|
191
|
-
attr_reader :options
|
192
|
-
|
193
|
-
def initialize(options = {})
|
194
|
-
@options = Utils::ImmutableHash.new(options, self.class.default_options)
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
module Template
|
199
|
-
include DefaultOptions
|
200
|
-
|
201
|
-
def engine(engine = nil)
|
202
|
-
default_options[:engine] = engine if engine
|
203
|
-
default_options[:engine]
|
204
|
-
end
|
205
|
-
|
206
|
-
def build_engine(*options)
|
207
|
-
raise 'No engine configured' unless engine
|
208
|
-
options << default_options
|
209
|
-
engine.new(Utils::ImmutableHash.new(*options)) do |e|
|
210
|
-
chain.each {|block| e.instance_eval(&block) }
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
def chain(&block)
|
215
|
-
chain = (default_options[:chain] ||= [])
|
216
|
-
chain << block if block
|
217
|
-
chain
|
218
|
-
end
|
219
|
-
end
|
220
135
|
end
|
221
136
|
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
module Temple
|
2
|
+
module Mixins
|
3
|
+
# @api private
|
4
|
+
module GrammarDSL
|
5
|
+
class Rule
|
6
|
+
def initialize(grammar)
|
7
|
+
@grammar = grammar
|
8
|
+
end
|
9
|
+
|
10
|
+
def |(rule)
|
11
|
+
Or.new(@grammar, self, rule)
|
12
|
+
end
|
13
|
+
|
14
|
+
def copy_to(grammar)
|
15
|
+
copy = dup.instance_eval { @grammar = grammar; self }
|
16
|
+
copy.after_copy(self) if copy.respond_to?(:after_copy)
|
17
|
+
copy
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Or < Rule
|
22
|
+
def initialize(grammar, *children)
|
23
|
+
super(grammar)
|
24
|
+
@children = children.map {|rule| @grammar.Rule(rule) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def <<(rule)
|
28
|
+
@children << @grammar.Rule(rule)
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
alias | <<
|
33
|
+
|
34
|
+
def match(exp, unmatched)
|
35
|
+
tmp = []
|
36
|
+
@children.any? {|rule| rule.match(exp, tmp) } || (unmatched.push(*tmp) && false)
|
37
|
+
end
|
38
|
+
|
39
|
+
def after_copy(source)
|
40
|
+
@children = @children.map {|child| child.copy_to(@grammar) }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Root < Or
|
45
|
+
def initialize(grammar, name)
|
46
|
+
super(grammar)
|
47
|
+
@name = name.to_sym
|
48
|
+
end
|
49
|
+
|
50
|
+
def match(exp, unmatched)
|
51
|
+
success = super
|
52
|
+
unmatched << [@name, exp] unless success
|
53
|
+
success
|
54
|
+
end
|
55
|
+
|
56
|
+
def match?(exp)
|
57
|
+
match(exp, [])
|
58
|
+
end
|
59
|
+
|
60
|
+
def validate!(exp)
|
61
|
+
unmatched = []
|
62
|
+
unless match(exp, unmatched)
|
63
|
+
require 'pp'
|
64
|
+
rule, exp = unmatched.sort_by {|r,e| [*e].flatten.size }.first
|
65
|
+
raise(InvalidExpression, PP.pp(exp, "#{@grammar}::#{rule} did not match\n"))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def copy_to(grammar)
|
70
|
+
grammar.const_defined?(@name) ? grammar.const_get(@name) : super
|
71
|
+
end
|
72
|
+
|
73
|
+
def after_copy(source)
|
74
|
+
@grammar.const_set(@name, self)
|
75
|
+
super
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class Element < Or
|
80
|
+
def initialize(grammar, rule)
|
81
|
+
super(grammar)
|
82
|
+
@rule = grammar.Rule(rule)
|
83
|
+
end
|
84
|
+
|
85
|
+
def match(exp, unmatched)
|
86
|
+
return false unless Array === exp && !exp.empty?
|
87
|
+
head, *tail = exp
|
88
|
+
@rule.match(head, unmatched) && super(tail, unmatched)
|
89
|
+
end
|
90
|
+
|
91
|
+
def after_copy(source)
|
92
|
+
@children = @children.map do |child|
|
93
|
+
child == source ? self : child.copy_to(@grammar)
|
94
|
+
end
|
95
|
+
@rule = @rule.copy_to(@grammar)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class Value < Rule
|
100
|
+
def initialize(grammar, value)
|
101
|
+
super(grammar)
|
102
|
+
@value = value
|
103
|
+
end
|
104
|
+
|
105
|
+
def match(exp, unmatched)
|
106
|
+
@value === exp
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def extended(mod)
|
111
|
+
mod.extend GrammarDSL
|
112
|
+
constants.each do |name|
|
113
|
+
const_get(name).copy_to(mod) if Rule === const_get(name)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def match?(exp)
|
118
|
+
const_get(:Expression).match?(exp)
|
119
|
+
end
|
120
|
+
|
121
|
+
def validate!(exp)
|
122
|
+
const_get(:Expression).validate!(exp)
|
123
|
+
end
|
124
|
+
|
125
|
+
alias === match?
|
126
|
+
alias =~ match?
|
127
|
+
|
128
|
+
def Value(value)
|
129
|
+
Value.new(self, value)
|
130
|
+
end
|
131
|
+
|
132
|
+
def Rule(rule)
|
133
|
+
case rule
|
134
|
+
when Rule
|
135
|
+
rule
|
136
|
+
when Symbol, Class, true, false, nil
|
137
|
+
Value(rule)
|
138
|
+
when Array
|
139
|
+
start = Or.new(self)
|
140
|
+
curr = [start]
|
141
|
+
rule.each do |elem|
|
142
|
+
if elem =~ /^(.*)(\*|\?|\+)$/
|
143
|
+
elem = Element.new(self, const_get($1))
|
144
|
+
curr.each {|c| c << elem }
|
145
|
+
elem << elem if $2 != '?'
|
146
|
+
curr = $2 == '+' ? [elem] : (curr << elem)
|
147
|
+
else
|
148
|
+
elem = Element.new(self, elem)
|
149
|
+
curr.each {|c| c << elem }
|
150
|
+
curr = [elem]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
elem = Value([])
|
154
|
+
curr.each {|c| c << elem }
|
155
|
+
start
|
156
|
+
else
|
157
|
+
raise "Invalid grammar rule '#{rule.inspect}'"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def const_missing(name)
|
162
|
+
const_set(name, Root.new(self, name))
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Temple
|
2
|
+
module Mixins
|
3
|
+
# @api public
|
4
|
+
module DefaultOptions
|
5
|
+
def set_default_options(options)
|
6
|
+
default_options.update(options)
|
7
|
+
end
|
8
|
+
|
9
|
+
def default_options
|
10
|
+
@default_options ||= MutableHash.new(superclass.respond_to?(:default_options) ?
|
11
|
+
superclass.default_options : nil)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# @api public
|
16
|
+
module Options
|
17
|
+
def self.included(base)
|
18
|
+
base.class_eval { extend DefaultOptions }
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :options
|
22
|
+
|
23
|
+
def initialize(options = {})
|
24
|
+
@options = ImmutableHash.new(options, self.class.default_options)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Temple
|
2
|
+
module Mixins
|
3
|
+
# @api private
|
4
|
+
module Template
|
5
|
+
include DefaultOptions
|
6
|
+
|
7
|
+
def engine(engine = nil)
|
8
|
+
default_options[:engine] = engine if engine
|
9
|
+
default_options[:engine]
|
10
|
+
end
|
11
|
+
|
12
|
+
def build_engine(*options)
|
13
|
+
raise 'No engine configured' unless engine
|
14
|
+
options << default_options
|
15
|
+
engine.new(ImmutableHash.new(*options))
|
16
|
+
end
|
17
|
+
|
18
|
+
def chain(&block)
|
19
|
+
chain = (default_options[:chain] ||= [])
|
20
|
+
chain << block if block
|
21
|
+
chain
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/temple/templates.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
module Temple
|
2
|
+
# @api public
|
2
3
|
module Templates
|
3
4
|
autoload :Tilt, 'temple/templates/tilt'
|
4
5
|
autoload :Rails, 'temple/templates/rails'
|
@@ -7,6 +8,7 @@ module Temple
|
|
7
8
|
template = Class.new(const_get(name))
|
8
9
|
template.engine(engine)
|
9
10
|
template.register_as(options[:register_as]) if options[:register_as]
|
11
|
+
template.default_options.update(options)
|
10
12
|
template
|
11
13
|
end
|
12
14
|
end
|
data/lib/temple/utils.rb
CHANGED
@@ -1,62 +1,13 @@
|
|
1
1
|
module Temple
|
2
|
+
# @api public
|
2
3
|
module Utils
|
3
4
|
extend self
|
4
5
|
|
5
|
-
class ImmutableHash
|
6
|
-
include Enumerable
|
7
|
-
|
8
|
-
def initialize(*hash)
|
9
|
-
@hash = hash.compact
|
10
|
-
end
|
11
|
-
|
12
|
-
def include?(key)
|
13
|
-
@hash.any? {|h| h.include?(key) }
|
14
|
-
end
|
15
|
-
|
16
|
-
def [](key)
|
17
|
-
@hash.each {|h| return h[key] if h.include?(key) }
|
18
|
-
nil
|
19
|
-
end
|
20
|
-
|
21
|
-
def each
|
22
|
-
keys.each {|k| yield(k, self[k]) }
|
23
|
-
end
|
24
|
-
|
25
|
-
def keys
|
26
|
-
@hash.inject([]) {|keys, h| keys += h.keys }.uniq
|
27
|
-
end
|
28
|
-
|
29
|
-
def values
|
30
|
-
keys.map {|k| self[k] }
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
class MutableHash < ImmutableHash
|
35
|
-
def initialize(*hash)
|
36
|
-
super({}, *hash)
|
37
|
-
end
|
38
|
-
|
39
|
-
def []=(key, value)
|
40
|
-
@hash.first[key] = value
|
41
|
-
end
|
42
|
-
|
43
|
-
def update(hash)
|
44
|
-
@hash.first.update(hash)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def indent(text, indent, pre_tags)
|
49
|
-
text = text.to_s
|
50
|
-
text.gsub!("\n", indent) if pre_tags !~ text
|
51
|
-
text
|
52
|
-
end
|
53
|
-
|
54
6
|
# Returns an escaped copy of `html`.
|
55
7
|
# Strings which are declared as html_safe are not escaped.
|
56
8
|
#
|
57
9
|
# @param html [String] The string to escape
|
58
10
|
# @return [String] The escaped string
|
59
|
-
# @api public
|
60
11
|
def escape_html_safe(html)
|
61
12
|
html.html_safe? ? html : escape_html(html)
|
62
13
|
end
|
@@ -66,7 +17,6 @@ module Temple
|
|
66
17
|
#
|
67
18
|
# @param html [String] The string to escape
|
68
19
|
# @return [String] The escaped string
|
69
|
-
# @api public
|
70
20
|
def escape_html(html)
|
71
21
|
EscapeUtils.escape_html(html.to_s)
|
72
22
|
end
|
@@ -85,7 +35,6 @@ module Temple
|
|
85
35
|
#
|
86
36
|
# @param html [String] The string to escape
|
87
37
|
# @return [String] The escaped string
|
88
|
-
# @api public
|
89
38
|
def escape_html(html)
|
90
39
|
html.to_s.gsub(/[&\"<>\/]/, ESCAPE_HTML)
|
91
40
|
end
|
@@ -94,20 +43,25 @@ module Temple
|
|
94
43
|
#
|
95
44
|
# @param html [String] The string to escape
|
96
45
|
# @return [String] The escaped string
|
97
|
-
# @api public
|
98
46
|
def escape_html(html)
|
99
47
|
html.to_s.gsub(/&/n, '&').gsub(/\"/n, '"').gsub(/>/n, '>').gsub(/</n, '<').gsub(/\//, '/')
|
100
48
|
end
|
101
49
|
end
|
102
50
|
|
103
|
-
# Generate unique
|
51
|
+
# Generate unique variable name
|
104
52
|
#
|
53
|
+
# @param prefix [String] Variable name prefix
|
105
54
|
# @return [String] Variable name
|
106
|
-
def
|
107
|
-
@
|
108
|
-
|
55
|
+
def unique_name(prefix = nil)
|
56
|
+
@unique_name ||= 0
|
57
|
+
prefix ||= (@unique_prefix ||= self.class.name.gsub('::', '_').downcase)
|
58
|
+
"_#{prefix}#{@unique_name += 1}"
|
109
59
|
end
|
110
60
|
|
61
|
+
# Check if expression is empty
|
62
|
+
#
|
63
|
+
# @param exp [Array] Temple expression
|
64
|
+
# @return true if expression is empty
|
111
65
|
def empty_exp?(exp)
|
112
66
|
case exp[0]
|
113
67
|
when :multi
|