temple 0.3.4 → 0.3.5
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/CHANGES +14 -0
- data/README.md +66 -50
- data/lib/temple/erb/parser.rb +15 -14
- data/lib/temple/erb/trimming.rb +9 -14
- data/lib/temple/generators.rb +3 -8
- data/lib/temple/html/attribute_merger.rb +5 -2
- data/lib/temple/html/fast.rb +7 -9
- data/lib/temple/html/pretty.rb +6 -5
- data/lib/temple/mixins/dispatcher.rb +96 -24
- data/lib/temple/mixins/engine_dsl.rb +15 -11
- data/lib/temple/version.rb +1 -1
- data/test/html/test_attribute_merger.rb +41 -0
- data/test/html/test_pretty.rb +1 -1
- data/test/mixins/test_dispatcher.rb +26 -0
- data/test/test_engine.rb +1 -1
- metadata +54 -45
data/CHANGES
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
master
|
2
2
|
|
3
|
+
0.3.5
|
4
|
+
|
5
|
+
* Temple::HTML::Pretty improved
|
6
|
+
* :sort_attrs option (default: true) added to HTML::AttributeMerger;
|
7
|
+
if set to false, the attributes will appear in the insertion order
|
8
|
+
* Temple::Mixins::EngineDSL api changed ("wildcard" is deprecated, use "use" instead)
|
9
|
+
* Temple::Mixins::CompiledDispatcher supports arbitrary levels now
|
10
|
+
* Don't use gsub! on incoming strings (#57)
|
11
|
+
* Fix newlines in erb parser (#46)
|
12
|
+
|
13
|
+
0.3.4
|
14
|
+
|
15
|
+
* Bugfix release (0.3.3 was yanked)
|
16
|
+
|
3
17
|
0.3.3
|
4
18
|
|
5
19
|
* Support for rails 3.1 streaming
|
data/README.md
CHANGED
@@ -35,23 +35,27 @@ Temple is built on a theory that every template consists of three elements:
|
|
35
35
|
The goal of a template engine is to take the template and eventually compile
|
36
36
|
it into *the core abstraction*:
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
38
|
+
```ruby
|
39
|
+
[:multi,
|
40
|
+
[:static, "Hello "],
|
41
|
+
[:dynamic, "@user.name"],
|
42
|
+
[:static, "!\n"],
|
43
|
+
[:code, "if @user.birthday == Date.today"],
|
44
|
+
[:static, "Happy birthday!"],
|
45
|
+
[:code, "end"]]
|
46
|
+
```
|
45
47
|
|
46
48
|
Then you can apply some optimizations, feed it to Temple and it generates fast
|
47
49
|
Ruby code for you:
|
48
50
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
51
|
+
```ruby
|
52
|
+
_buf = []
|
53
|
+
_buf << ("Hello #{@user.name}!\n")
|
54
|
+
if @user.birthday == Date.today
|
55
|
+
_buf << "Happy birthday!"
|
56
|
+
end
|
57
|
+
_buf.join
|
58
|
+
```
|
55
59
|
|
56
60
|
S-expression
|
57
61
|
------------
|
@@ -67,13 +71,15 @@ manipulated by computers.
|
|
67
71
|
|
68
72
|
Some examples:
|
69
73
|
|
70
|
-
|
74
|
+
```ruby
|
75
|
+
[:static, "Hello World!"]
|
71
76
|
|
72
|
-
|
73
|
-
|
74
|
-
|
77
|
+
[:multi,
|
78
|
+
[:static, "Hello "],
|
79
|
+
[:dynamic, "@world"]]
|
75
80
|
|
76
|
-
|
81
|
+
[:html, :tag, "em", [:html, :attrs], [:static, "Hey hey"]]
|
82
|
+
```
|
77
83
|
|
78
84
|
*NOTE:* SexpProcessor, a library written by Ryan Davis, includes a `Sexp`
|
79
85
|
class. While you can use this class (since it's a subclass of Array), it's not
|
@@ -89,11 +95,13 @@ do one thing at every step.
|
|
89
95
|
|
90
96
|
So what's an abstraction? An abstraction is when you introduce a new types:
|
91
97
|
|
92
|
-
|
93
|
-
|
98
|
+
```ruby
|
99
|
+
# Instead of:
|
100
|
+
[:static, "<strong>Use the force</strong>"]
|
94
101
|
|
95
|
-
|
96
|
-
|
102
|
+
# You use:
|
103
|
+
[:html, :tag, "strong", [:html, :attrs], [:static, "Use the force"]]
|
104
|
+
```
|
97
105
|
|
98
106
|
### Why are abstractions so important?
|
99
107
|
|
@@ -126,15 +134,17 @@ the argument, and it should be possible to use the same instance several times
|
|
126
134
|
While a compiler can be any object, you very often want to structure it as a
|
127
135
|
class. Temple then assumes the initializer takes an optional option hash:
|
128
136
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
137
|
+
```ruby
|
138
|
+
class MyCompiler
|
139
|
+
def initialize(options = {})
|
140
|
+
@options = options
|
141
|
+
end
|
133
142
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
143
|
+
def call(exp)
|
144
|
+
# do stuff
|
145
|
+
end
|
146
|
+
end
|
147
|
+
```
|
138
148
|
|
139
149
|
### Parsers
|
140
150
|
|
@@ -181,24 +191,26 @@ Engines
|
|
181
191
|
|
182
192
|
When you have a chain of a parsers, some filters and a generator you can finally create your *engine*. Temple provides {Temple::Engine} which makes this very easy:
|
183
193
|
|
184
|
-
|
185
|
-
|
186
|
-
|
194
|
+
```ruby
|
195
|
+
class MyEngine < Temple::Engine
|
196
|
+
# First run MyParser, passing the :strict option
|
197
|
+
use MyParser, :strict
|
187
198
|
|
188
|
-
|
189
|
-
|
199
|
+
# Then a custom filter
|
200
|
+
use MyFilter
|
190
201
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
202
|
+
# Then some general optimizations filters
|
203
|
+
filter :MultiFlattener
|
204
|
+
filter :StaticMerger
|
205
|
+
filter :DynamicInliner
|
195
206
|
|
196
|
-
|
197
|
-
|
198
|
-
|
207
|
+
# Finally the generator
|
208
|
+
generator :ArrayBuffer, :buffer
|
209
|
+
end
|
199
210
|
|
200
|
-
|
201
|
-
|
211
|
+
engine = MyEngine.new(:strict => "For MyParser")
|
212
|
+
engine.call(something)
|
213
|
+
```
|
202
214
|
|
203
215
|
And then?
|
204
216
|
---------
|
@@ -209,13 +221,15 @@ generator. What happens next?
|
|
209
221
|
Temple provides helpers to create template classes for [Tilt](http://github.com/rtomayko/tilt) and
|
210
222
|
Rails.
|
211
223
|
|
212
|
-
|
224
|
+
```ruby
|
225
|
+
require 'tilt'
|
213
226
|
|
214
|
-
|
215
|
-
|
227
|
+
# Create template class MyTemplate and register your file extension
|
228
|
+
MyTemplate = Temple::Templates::Tilt(MyEngine, :register_as => 'ext')
|
216
229
|
|
217
|
-
|
218
|
-
|
230
|
+
Tilt.new('example.ext').render # => Render a file
|
231
|
+
MyTemplate.new { "String" }.render # => Render a string
|
232
|
+
```
|
219
233
|
|
220
234
|
Installation
|
221
235
|
------------
|
@@ -223,7 +237,9 @@ Installation
|
|
223
237
|
You need Ruby 1.8.7 or Ruby 1.9.2 to work with Temple. Temple is published as a Ruby Gem which can be installed
|
224
238
|
as following:
|
225
239
|
|
226
|
-
|
240
|
+
```bash
|
241
|
+
$ gem install temple
|
242
|
+
```
|
227
243
|
|
228
244
|
Engines using Temple
|
229
245
|
--------------------
|
data/lib/temple/erb/parser.rb
CHANGED
@@ -6,27 +6,28 @@ module Temple
|
|
6
6
|
class Parser
|
7
7
|
include Mixins::Options
|
8
8
|
|
9
|
-
ERB_PATTERN = /(
|
10
|
-
|
11
|
-
ESCAPED = {
|
12
|
-
'<%%' => '<%',
|
13
|
-
'%%>' => '%>',
|
14
|
-
}.freeze
|
9
|
+
ERB_PATTERN = /(\n|<%%|%%>)|<%(==?|\#)?(.*?)?-?%>/m
|
15
10
|
|
16
11
|
def call(input)
|
17
12
|
result = [:multi]
|
18
13
|
pos = 0
|
19
|
-
input.scan(ERB_PATTERN) do |
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
14
|
+
input.scan(ERB_PATTERN) do |token, indicator, code|
|
15
|
+
text = input[pos...$~.begin(0)]
|
16
|
+
pos = $~.end(0)
|
17
|
+
if token
|
18
|
+
case token
|
19
|
+
when "\n"
|
20
|
+
result << [:static, "#{text}\n"] << [:newline]
|
21
|
+
when '<%%', '%%>'
|
22
|
+
result << [:static, text] unless text.empty?
|
23
|
+
token.slice!(1)
|
24
|
+
result << [:static, token]
|
25
|
+
end
|
26
26
|
else
|
27
|
+
result << [:static, text] unless text.empty?
|
27
28
|
case indicator
|
28
29
|
when '#'
|
29
|
-
code.count("\n")
|
30
|
+
result << [:code, "\n" * code.count("\n")]
|
30
31
|
when /=/
|
31
32
|
result << [:escape, indicator.size <= 1, [:dynamic, code]]
|
32
33
|
else
|
data/lib/temple/erb/trimming.rb
CHANGED
@@ -10,18 +10,17 @@ module Temple
|
|
10
10
|
def on_multi(*exps)
|
11
11
|
case options[:trim_mode]
|
12
12
|
when '>'
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
i = 0
|
14
|
+
while i < exps.size
|
15
|
+
exps.delete_at(i + 1) if code?(exps[i]) && exps[i+1] == [:static, "\n"]
|
16
|
+
i += 1
|
17
17
|
end
|
18
18
|
when '<>'
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
19
|
+
i = 0
|
20
|
+
while i < exps.size
|
21
|
+
exps.delete_at(i + 1) if code?(exps[i]) && exps[i+1] == [:static, "\n"] &&
|
22
|
+
(!exps[i-1] || (exps[i-1] == [:newline]))
|
23
|
+
i += 1
|
25
24
|
end
|
26
25
|
end
|
27
26
|
[:multi, *exps]
|
@@ -32,10 +31,6 @@ module Temple
|
|
32
31
|
def code?(exp)
|
33
32
|
exp[0] == :escape || exp[0] == :code
|
34
33
|
end
|
35
|
-
|
36
|
-
def static?(exp)
|
37
|
-
exp[0] == :static
|
38
|
-
end
|
39
34
|
end
|
40
35
|
end
|
41
36
|
end
|
data/lib/temple/generators.rb
CHANGED
@@ -11,6 +11,7 @@ module Temple
|
|
11
11
|
#
|
12
12
|
# @api public
|
13
13
|
class Generator
|
14
|
+
include Mixins::CompiledDispatcher
|
14
15
|
include Mixins::Options
|
15
16
|
|
16
17
|
default_options[:buffer] = '_buf'
|
@@ -19,14 +20,8 @@ module Temple
|
|
19
20
|
[preamble, compile(exp), postamble].join('; ')
|
20
21
|
end
|
21
22
|
|
22
|
-
def
|
23
|
-
|
24
|
-
method = "on_#{type}"
|
25
|
-
if respond_to?(method)
|
26
|
-
send(method, *args)
|
27
|
-
else
|
28
|
-
raise InvalidExpression, "Generator supports only core expressions - found #{exp.inspect}"
|
29
|
-
end
|
23
|
+
def on(*exp)
|
24
|
+
raise InvalidExpression, "Generator supports only core expressions - found #{exp.inspect}"
|
30
25
|
end
|
31
26
|
|
32
27
|
def on_multi(*exp)
|
@@ -3,13 +3,14 @@ module Temple
|
|
3
3
|
# @api public
|
4
4
|
class AttributeMerger < Filter
|
5
5
|
default_options[:attr_delimiter] = {'id' => '_', 'class' => ' '}
|
6
|
+
default_options[:sort_attrs] = true
|
6
7
|
|
7
8
|
def on_html_attrs(*attrs)
|
9
|
+
names = []
|
8
10
|
result = {}
|
9
11
|
attrs.each do |attr|
|
10
12
|
raise(InvalidExpression, 'Attribute is not a html attr') if attr[0] != :html || attr[1] != :attr
|
11
13
|
name, value = attr[2].to_s, attr[3]
|
12
|
-
next if empty_exp?(value)
|
13
14
|
if result[name]
|
14
15
|
delimiter = options[:attr_delimiter][name]
|
15
16
|
raise "Multiple #{name} attributes specified" unless delimiter
|
@@ -37,9 +38,11 @@ module Temple
|
|
37
38
|
end
|
38
39
|
else
|
39
40
|
result[name] = attr
|
41
|
+
names << name
|
40
42
|
end
|
41
43
|
end
|
42
|
-
[:
|
44
|
+
result = options[:sort_attrs] ? result.sort : names.map {|k| [k, result[k]] }
|
45
|
+
[:multi, *result.map {|name,attr| compile(attr) }]
|
43
46
|
end
|
44
47
|
|
45
48
|
def on_html_attr(name, value)
|
data/lib/temple/html/fast.rb
CHANGED
@@ -43,21 +43,19 @@ module Temple
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def on_html_doctype(type)
|
46
|
-
type = type.to_s
|
47
|
-
trailing_newlines = type[/(\A|[^\r])(\n+)\Z/, 2].to_s
|
48
|
-
text = type.downcase.strip
|
46
|
+
type = type.to_s.downcase
|
49
47
|
|
50
|
-
if
|
48
|
+
if type =~ /^xml(\s+(.+))?$/
|
51
49
|
raise 'Invalid xml directive in html mode' if html?
|
52
|
-
|
53
|
-
str = "<?xml version=#{
|
50
|
+
w = options[:attr_wrapper]
|
51
|
+
str = "<?xml version=#{w}1.0#{w} encoding=#{w}#{$2 || 'utf-8'}#{w} ?>"
|
54
52
|
elsif html?
|
55
|
-
str = HTML_DOCTYPES[
|
53
|
+
str = HTML_DOCTYPES[type] || raise("Invalid html doctype #{type}")
|
56
54
|
else
|
57
|
-
str = XHTML_DOCTYPES[
|
55
|
+
str = XHTML_DOCTYPES[type] || raise("Invalid xhtml doctype #{type}")
|
58
56
|
end
|
59
57
|
|
60
|
-
[:static, str
|
58
|
+
[:static, str]
|
61
59
|
end
|
62
60
|
|
63
61
|
def on_html_comment(content)
|
data/lib/temple/html/pretty.rb
CHANGED
@@ -6,7 +6,7 @@ module Temple
|
|
6
6
|
:pretty => true,
|
7
7
|
:indent_tags => %w(article aside audio base body datalist dd div dl dt
|
8
8
|
fieldset figure footer form head h1 h2 h3 h4 h5 h6
|
9
|
-
header hgroup hr html
|
9
|
+
header hgroup hr html input li link meta nav ol p
|
10
10
|
rp rt ruby section script style table tbody td tfoot
|
11
11
|
th thead title tr ul video).freeze,
|
12
12
|
:pre_tags => %w(code pre textarea).freeze
|
@@ -25,7 +25,7 @@ module Temple
|
|
25
25
|
|
26
26
|
def on_static(content)
|
27
27
|
if @pretty
|
28
|
-
content.gsub
|
28
|
+
content = content.gsub("\n", indent) if @pre_tags !~ content
|
29
29
|
@last = content.sub!(/\r?\n\s*$/, ' ') ? nil : :noindent
|
30
30
|
end
|
31
31
|
[:static, content]
|
@@ -38,7 +38,7 @@ module Temple
|
|
38
38
|
gsub_code = if ''.respond_to?(:html_safe?)
|
39
39
|
"#{tmp} = #{tmp}.html_safe? ? #{tmp}.gsub(\"\\n\", #{indent.inspect}).html_safe : #{tmp}.gsub(\"\\n\", #{indent.inspect})"
|
40
40
|
else
|
41
|
-
"#{tmp}.gsub
|
41
|
+
"#{tmp} = #{tmp}.gsub(\"\\n\", #{indent.inspect})"
|
42
42
|
end
|
43
43
|
[:multi,
|
44
44
|
[:code, "#{tmp} = (#{code}).to_s"],
|
@@ -56,8 +56,9 @@ module Temple
|
|
56
56
|
|
57
57
|
def on_html_comment(content)
|
58
58
|
return super unless @pretty
|
59
|
+
result = [:multi, [:static, tag_indent(nil)], super]
|
59
60
|
@last = nil
|
60
|
-
|
61
|
+
result
|
61
62
|
end
|
62
63
|
|
63
64
|
def on_html_tag(name, attrs, content = nil)
|
@@ -76,8 +77,8 @@ module Temple
|
|
76
77
|
result << compile(content)
|
77
78
|
@indent -= 1
|
78
79
|
end
|
80
|
+
result << [:static, "#{content && !empty_exp?(content) ? tag_indent(name) : ''}</#{name}>"] unless closed
|
79
81
|
|
80
|
-
result << [:static, "#{tag_indent(name)}</#{name}>"] if !closed
|
81
82
|
@pretty = true
|
82
83
|
result
|
83
84
|
end
|
@@ -51,38 +51,19 @@ module Temple
|
|
51
51
|
|
52
52
|
private
|
53
53
|
|
54
|
-
def case_statement(types)
|
55
|
-
code = "type, *args = args\ncase type\n"
|
56
|
-
types.each do |name, method|
|
57
|
-
code << "when #{name.to_sym.inspect}\n" <<
|
58
|
-
(Hash === method ? case_statement(method) : "#{method}(*args)\n")
|
59
|
-
end
|
60
|
-
code << "else\nexp\nend\n"
|
61
|
-
end
|
62
|
-
|
63
54
|
def dispatcher(exp)
|
64
55
|
replace_dispatcher(exp)
|
65
56
|
end
|
66
57
|
|
67
58
|
def replace_dispatcher(exp)
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
method_types = $1.split('_')
|
72
|
-
(0...method_types.size).inject(types) do |tmp, i|
|
73
|
-
raise "Invalid temple dispatcher #{method}" unless Hash === tmp
|
74
|
-
if i == method_types.size - 1
|
75
|
-
tmp[method_types[i]] = method
|
76
|
-
else
|
77
|
-
tmp[method_types[i]] ||= {}
|
78
|
-
end
|
79
|
-
end
|
59
|
+
tree = DispatchNode.new
|
60
|
+
dispatched_methods.each do |method|
|
61
|
+
method.split('_')[1..-1].inject(tree) {|node, type| node[type.to_sym] }.method = method
|
80
62
|
end
|
81
63
|
self.class.class_eval %{
|
82
64
|
def dispatcher(exp)
|
83
65
|
if self.class == #{self.class}
|
84
|
-
|
85
|
-
#{case_statement(types)}
|
66
|
+
#{tree.compile}
|
86
67
|
else
|
87
68
|
replace_dispatcher(exp)
|
88
69
|
end
|
@@ -90,9 +71,100 @@ module Temple
|
|
90
71
|
}
|
91
72
|
dispatcher(exp)
|
92
73
|
end
|
74
|
+
|
75
|
+
def dispatched_methods
|
76
|
+
re = /^on(_[a-zA-Z0-9]+)*$/
|
77
|
+
self.methods.map(&:to_s).select(&re.method(:=~))
|
78
|
+
end
|
79
|
+
|
80
|
+
# @api private
|
81
|
+
class DispatchNode < Hash
|
82
|
+
attr_accessor :method
|
83
|
+
|
84
|
+
def initialize
|
85
|
+
super { |hsh,key| hsh[key] = DispatchNode.new }
|
86
|
+
@method = nil
|
87
|
+
end
|
88
|
+
|
89
|
+
def compile(level = 0, parent = nil)
|
90
|
+
if empty?
|
91
|
+
if method
|
92
|
+
(' ' * level) + "#{method}(*exp[#{level}..-1])"
|
93
|
+
elsif !parent
|
94
|
+
'exp'
|
95
|
+
else
|
96
|
+
raise 'Invalid dispatcher node'
|
97
|
+
end
|
98
|
+
else
|
99
|
+
code = [(' ' * level) + "case(exp[#{level}])"]
|
100
|
+
each do |key, child|
|
101
|
+
code << (' ' * level) + "when #{key.inspect}"
|
102
|
+
code << child.compile(level + 1, parent || method)
|
103
|
+
end
|
104
|
+
if method || !parent
|
105
|
+
code << (' ' * level) + "else"
|
106
|
+
if method
|
107
|
+
code << (' ' * level) + " #{method}(*exp[#{level}..-1])"
|
108
|
+
else
|
109
|
+
code << (' ' * level) + " exp"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
code << (' ' * level) + "end"
|
113
|
+
code.join("\n")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
93
117
|
end
|
94
118
|
|
95
|
-
# @api
|
119
|
+
# @api public
|
120
|
+
#
|
121
|
+
# Implements a compatible call-method
|
122
|
+
# based on the including classe's methods.
|
123
|
+
#
|
124
|
+
# It uses every method starting with
|
125
|
+
# "on" and uses the rest of the method
|
126
|
+
# name as prefix of the expression it
|
127
|
+
# will receive. So, if a dispatcher
|
128
|
+
# has a method named "on_x", this method
|
129
|
+
# will be called with arg0,..,argN
|
130
|
+
# whenever an expression like [:x, arg0,..,argN ]
|
131
|
+
# is encountered.
|
132
|
+
#
|
133
|
+
# This works with longer prefixes, too.
|
134
|
+
# For example a method named "on_y_z"
|
135
|
+
# will be called whenever an expression
|
136
|
+
# like [:y, :z, .. ] is found. Furthermore,
|
137
|
+
# if additionally a method named "on_y"
|
138
|
+
# is present, it will be called when an
|
139
|
+
# expression starts with :y but then does
|
140
|
+
# not contain with :z. This way a
|
141
|
+
# dispatcher can implement namespaces.
|
142
|
+
#
|
143
|
+
# @note
|
144
|
+
# Processing does not reach into unknown
|
145
|
+
# expression types by default.
|
146
|
+
#
|
147
|
+
# @example
|
148
|
+
# class MyAwesomeDispatch
|
149
|
+
# include Temple::Mixins::Dispatcher
|
150
|
+
# def on_awesome(thing) # keep awesome things
|
151
|
+
# return [:awesome, thing]
|
152
|
+
# end
|
153
|
+
# def on_boring(thing) # make boring things awesome
|
154
|
+
# return [:awesome, thing+" with bacon"]
|
155
|
+
# end
|
156
|
+
# def on(type,*args) # unknown stuff is boring too
|
157
|
+
# return [:awesome, 'just bacon']
|
158
|
+
# end
|
159
|
+
# end
|
160
|
+
# filter = MyAwesomeDispatch.new
|
161
|
+
# # Boring things are converted:
|
162
|
+
# filter.call([:boring, 'egg']) #=> [:awesome, 'egg with bacon']
|
163
|
+
# # Unknown things too:
|
164
|
+
# filter.call([:foo]) #=> [:awesome, 'just bacon']
|
165
|
+
# # Known but not boring things won't be touched:
|
166
|
+
# filter.call([:awesome, 'chuck norris']) #=>[:awesome, 'chuck norris']
|
167
|
+
#
|
96
168
|
module Dispatcher
|
97
169
|
include CompiledDispatcher
|
98
170
|
include CoreDispatcher
|
@@ -28,6 +28,16 @@ module Temple
|
|
28
28
|
|
29
29
|
alias use append
|
30
30
|
|
31
|
+
# DEPRECATED!
|
32
|
+
#
|
33
|
+
# wildcard(:FilterName) { FilterClass.new(options) }
|
34
|
+
#
|
35
|
+
# is replaced by
|
36
|
+
#
|
37
|
+
# use(:FilterName) { FilterClass.new(options) }
|
38
|
+
#
|
39
|
+
alias wildcard use
|
40
|
+
|
31
41
|
def before(name, *args, &block)
|
32
42
|
name = Class === name ? name.name.to_sym : name
|
33
43
|
raise(ArgumentError, 'First argument must be Class or Symbol') unless Symbol === name
|
@@ -78,18 +88,12 @@ module Temple
|
|
78
88
|
chain_modified!
|
79
89
|
end
|
80
90
|
|
81
|
-
def filter(name, *options
|
82
|
-
use(name, Temple::Filters.const_get(name), *options
|
83
|
-
end
|
84
|
-
|
85
|
-
def generator(name, *options, &block)
|
86
|
-
use(name, Temple::Generators.const_get(name), *options, &block)
|
91
|
+
def filter(name, *options)
|
92
|
+
use(name, Temple::Filters.const_get(name), *options)
|
87
93
|
end
|
88
94
|
|
89
|
-
def
|
90
|
-
|
91
|
-
chain << [name, define_chain_method("WILDCARD #{name}", block)]
|
92
|
-
chain_modified!
|
95
|
+
def generator(name, *options)
|
96
|
+
use(name, Temple::Generators.const_get(name), *options)
|
93
97
|
end
|
94
98
|
|
95
99
|
private
|
@@ -125,7 +129,7 @@ module Temple
|
|
125
129
|
# The proc is converted to a method of the engine class.
|
126
130
|
# The proc can then access the option hash of the engine.
|
127
131
|
raise(ArgumentError, 'Too many arguments') unless args.empty?
|
128
|
-
raise(ArgumentError, 'Proc or blocks must have arity 1')
|
132
|
+
raise(ArgumentError, 'Proc or blocks must have arity 0 or 1') if filter.arity > 1
|
129
133
|
[name, define_chain_method("FILTER #{name}", filter)]
|
130
134
|
when Class
|
131
135
|
# Class argument (e.g Filter class)
|
data/lib/temple/version.rb
CHANGED
@@ -3,6 +3,7 @@ require 'helper'
|
|
3
3
|
describe Temple::HTML::AttributeMerger do
|
4
4
|
before do
|
5
5
|
@merger = Temple::HTML::AttributeMerger.new
|
6
|
+
@ordered_merger = Temple::HTML::AttributeMerger.new :sort_attrs => false
|
6
7
|
end
|
7
8
|
|
8
9
|
it 'should pass static attributes through' do
|
@@ -16,6 +17,46 @@ describe Temple::HTML::AttributeMerger do
|
|
16
17
|
[:content]]
|
17
18
|
end
|
18
19
|
|
20
|
+
it 'should sort html attributes by name by default, when :sort_attrs is true' do
|
21
|
+
@merger.call([:html, :tag,
|
22
|
+
'meta',
|
23
|
+
[:html, :attrs, [:html, :attr, 'c', [:static, '1']],
|
24
|
+
[:html, :attr, 'd', [:static, '2']],
|
25
|
+
[:html, :attr, 'a', [:static, '3']],
|
26
|
+
[:html, :attr, 'b', [:static, '4']]]
|
27
|
+
]).should.equal [:html, :tag, 'meta',
|
28
|
+
[:multi,
|
29
|
+
[:html, :attr, 'a', [:static, '3']],
|
30
|
+
[:html, :attr, 'b', [:static, '4']],
|
31
|
+
[:html, :attr, 'c', [:static, '1']],
|
32
|
+
[:html, :attr, 'd', [:static, '2']]]]
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should preserve the order of html attributes when :sort_attrs is false' do
|
36
|
+
@ordered_merger.call([:html, :tag,
|
37
|
+
'meta',
|
38
|
+
[:html, :attrs, [:html, :attr, 'c', [:static, '1']],
|
39
|
+
[:html, :attr, 'd', [:static, '2']],
|
40
|
+
[:html, :attr, 'a', [:static, '3']],
|
41
|
+
[:html, :attr, 'b', [:static, '4']]]
|
42
|
+
]).should.equal [:html, :tag, 'meta',
|
43
|
+
[:multi,
|
44
|
+
[:html, :attr, 'c', [:static, '1']],
|
45
|
+
[:html, :attr, 'd', [:static, '2']],
|
46
|
+
[:html, :attr, 'a', [:static, '3']],
|
47
|
+
[:html, :attr, 'b', [:static, '4']]]]
|
48
|
+
|
49
|
+
# Use case:
|
50
|
+
@ordered_merger.call([:html, :tag,
|
51
|
+
'meta',
|
52
|
+
[:html, :attrs, [:html, :attr, 'http-equiv', [:static, 'Content-Type']],
|
53
|
+
[:html, :attr, 'content', [:static, '']]]
|
54
|
+
]).should.equal [:html, :tag, 'meta',
|
55
|
+
[:multi,
|
56
|
+
[:html, :attr, 'http-equiv', [:static, 'Content-Type']],
|
57
|
+
[:html, :attr, 'content', [:static, '']]]]
|
58
|
+
end
|
59
|
+
|
19
60
|
it 'should check for empty dynamic attribute' do
|
20
61
|
@merger.call([:html, :tag,
|
21
62
|
'div',
|
data/test/html/test_pretty.rb
CHANGED
@@ -22,7 +22,7 @@ describe Temple::HTML::Pretty do
|
|
22
22
|
[:static, "text"],
|
23
23
|
[:multi,
|
24
24
|
[:code, "_temple_html_pretty2 = (code).to_s"],
|
25
|
-
[:code, 'if _temple_html_pretty1 !~ _temple_html_pretty2; _temple_html_pretty2.gsub
|
25
|
+
[:code, 'if _temple_html_pretty1 !~ _temple_html_pretty2; _temple_html_pretty2 = _temple_html_pretty2.gsub("\n", "\n "); end'],
|
26
26
|
[:dynamic, "_temple_html_pretty2"]]],
|
27
27
|
[:static, "</p>"]],
|
28
28
|
[:static, "\n</div>"]]]
|
@@ -7,9 +7,23 @@ class FilterWithDispatcherMixin
|
|
7
7
|
[:on_test, arg]
|
8
8
|
end
|
9
9
|
|
10
|
+
def on_test_check(arg)
|
11
|
+
[:on_check, arg]
|
12
|
+
end
|
13
|
+
|
10
14
|
def on_second_test(arg)
|
11
15
|
[:on_second_test, arg]
|
12
16
|
end
|
17
|
+
|
18
|
+
def on_seventh_level_level_level_level_level_test(arg)
|
19
|
+
[:on_seventh_test, arg]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class FilterWithDispatcherMixinAndOn < FilterWithDispatcherMixin
|
24
|
+
def on(*args)
|
25
|
+
[:on_zero, *args]
|
26
|
+
end
|
13
27
|
end
|
14
28
|
|
15
29
|
describe Temple::Mixins::Dispatcher do
|
@@ -28,4 +42,16 @@ describe Temple::Mixins::Dispatcher do
|
|
28
42
|
it 'should dispatch second level' do
|
29
43
|
@filter.call([:second, :test, 42]).should.equal [:on_second_test, 42]
|
30
44
|
end
|
45
|
+
|
46
|
+
it 'should dispatch second level if prefixed' do
|
47
|
+
@filter.call([:test, :check, 42]).should.equal [:on_check, 42]
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should dispatch seventh level' do
|
51
|
+
@filter.call([:seventh, :level, :level, :level, :level, :level, :test, 42]).should == [:on_seventh_test, 42]
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should dispatch zero level' do
|
55
|
+
FilterWithDispatcherMixinAndOn.new.call([:foo,42]).should == [:on_zero, :foo, 42]
|
56
|
+
end
|
31
57
|
end
|
data/test/test_engine.rb
CHANGED
metadata
CHANGED
@@ -1,58 +1,63 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: temple
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.4
|
3
|
+
version: !ruby/object:Gem::Version
|
5
4
|
prerelease:
|
5
|
+
version: 0.3.5
|
6
6
|
platform: ruby
|
7
|
-
authors:
|
7
|
+
authors:
|
8
8
|
- Magnus Holm
|
9
9
|
- Daniel Mendler
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
|
14
|
+
date: 2012-01-06 00:00:00 +01:00
|
15
|
+
default_executable:
|
16
|
+
dependencies:
|
17
|
+
- !ruby/object:Gem::Dependency
|
16
18
|
name: tilt
|
17
|
-
|
19
|
+
prerelease: false
|
20
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
18
21
|
none: false
|
19
|
-
requirements:
|
20
|
-
- -
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version:
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: "0"
|
23
26
|
type: :development
|
24
|
-
|
25
|
-
|
26
|
-
- !ruby/object:Gem::Dependency
|
27
|
+
version_requirements: *id001
|
28
|
+
- !ruby/object:Gem::Dependency
|
27
29
|
name: bacon
|
28
|
-
|
30
|
+
prerelease: false
|
31
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
29
32
|
none: false
|
30
|
-
requirements:
|
31
|
-
- -
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: "0"
|
34
37
|
type: :development
|
35
|
-
|
36
|
-
|
37
|
-
- !ruby/object:Gem::Dependency
|
38
|
+
version_requirements: *id002
|
39
|
+
- !ruby/object:Gem::Dependency
|
38
40
|
name: rake
|
39
|
-
|
41
|
+
prerelease: false
|
42
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
40
43
|
none: false
|
41
|
-
requirements:
|
42
|
-
- -
|
43
|
-
- !ruby/object:Gem::Version
|
44
|
-
version:
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: "0"
|
45
48
|
type: :development
|
46
|
-
|
47
|
-
version_requirements: *70118806200640
|
49
|
+
version_requirements: *id003
|
48
50
|
description:
|
49
|
-
email:
|
51
|
+
email:
|
50
52
|
- judofyr@gmail.com
|
51
53
|
- mail@daniel-mendler.de
|
52
54
|
executables: []
|
55
|
+
|
53
56
|
extensions: []
|
57
|
+
|
54
58
|
extra_rdoc_files: []
|
55
|
-
|
59
|
+
|
60
|
+
files:
|
56
61
|
- .gitignore
|
57
62
|
- .travis.yml
|
58
63
|
- .yardopts
|
@@ -114,31 +119,35 @@ files:
|
|
114
119
|
- test/test_grammar.rb
|
115
120
|
- test/test_hash.rb
|
116
121
|
- test/test_utils.rb
|
122
|
+
has_rdoc: true
|
117
123
|
homepage: http://dojo.rubyforge.org/
|
118
124
|
licenses: []
|
125
|
+
|
119
126
|
post_install_message:
|
120
127
|
rdoc_options: []
|
121
|
-
|
128
|
+
|
129
|
+
require_paths:
|
122
130
|
- lib
|
123
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
131
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
132
|
none: false
|
125
|
-
requirements:
|
126
|
-
- -
|
127
|
-
- !ruby/object:Gem::Version
|
128
|
-
version:
|
129
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: "0"
|
137
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
138
|
none: false
|
131
|
-
requirements:
|
132
|
-
- -
|
133
|
-
- !ruby/object:Gem::Version
|
134
|
-
version:
|
139
|
+
requirements:
|
140
|
+
- - ">="
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: "0"
|
135
143
|
requirements: []
|
144
|
+
|
136
145
|
rubyforge_project:
|
137
|
-
rubygems_version: 1.
|
146
|
+
rubygems_version: 1.6.2
|
138
147
|
signing_key:
|
139
148
|
specification_version: 3
|
140
149
|
summary: Template compilation framework in Ruby
|
141
|
-
test_files:
|
150
|
+
test_files:
|
142
151
|
- test/filters/test_control_flow.rb
|
143
152
|
- test/filters/test_dynamic_inliner.rb
|
144
153
|
- test/filters/test_eraser.rb
|