temple 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|