mustache 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.md +13 -0
- data/README.md +55 -4
- data/Rakefile +13 -0
- data/examples/delimiters.html +6 -0
- data/examples/delimiters.rb +22 -0
- data/lib/mustache.rb +12 -1
- data/lib/mustache/sinatra.rb +4 -1
- data/lib/mustache/template.rb +40 -5
- data/lib/mustache/version.rb +1 -1
- data/test/mustache_test.rb +84 -5
- metadata +5 -2
data/HISTORY.md
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
## 0.3.0 (2009-??-??)
|
2
|
+
|
3
|
+
* Set Delimiter tags are now supported. See the README
|
4
|
+
* Improved error message when an enumerable section did not return all
|
5
|
+
hashes.
|
6
|
+
* Added a shortcut: if a section's value is a single hash, treat is as
|
7
|
+
a one element array whose value is the hash.
|
8
|
+
* Bugfix: String templates set at the class level were not compiled
|
9
|
+
* Added a class-level `compiled?` method for checking if a template
|
10
|
+
has been compiled.
|
11
|
+
* Added an instance-level `compiled?` method.
|
12
|
+
* Cache template compilation in Sinatra
|
13
|
+
|
1
14
|
## 0.2.2 (2009-10-11)
|
2
15
|
|
3
16
|
* Improved documentation
|
data/README.md
CHANGED
@@ -144,6 +144,24 @@ And this view code:
|
|
144
144
|
When rendered, our view will contain a list of all repository names in
|
145
145
|
the database.
|
146
146
|
|
147
|
+
As a convenience, if a section returns a hash (as opposed to an array
|
148
|
+
or a boolean) it will be treated as a single item array.
|
149
|
+
|
150
|
+
With the above template, we could use this Ruby code for a single
|
151
|
+
iteration:
|
152
|
+
|
153
|
+
def repo
|
154
|
+
{ :name => Repository.first.to_s }
|
155
|
+
end
|
156
|
+
|
157
|
+
This would be treated by Mustache as functionally equivalent to the
|
158
|
+
following:
|
159
|
+
|
160
|
+
def repo
|
161
|
+
[ { :name => Repository.first.to_s } ]
|
162
|
+
end
|
163
|
+
|
164
|
+
|
147
165
|
### Comments
|
148
166
|
|
149
167
|
Comments begin with a bang and are ignored. The following template:
|
@@ -165,6 +183,31 @@ In this way partials can reference variables or sections the calling
|
|
165
183
|
view defines.
|
166
184
|
|
167
185
|
|
186
|
+
### Set Delimiter
|
187
|
+
|
188
|
+
Set Delimiter tags start with an equal sign and change the tag
|
189
|
+
delimiters from {{ and }} to custom strings.
|
190
|
+
|
191
|
+
Consider the following contrived example:
|
192
|
+
|
193
|
+
* {{ default_tags }}
|
194
|
+
{{=<% %>=}}
|
195
|
+
* <% erb_style_tags %>
|
196
|
+
<%={{ }}=%>
|
197
|
+
* {{ default_tags_again }}
|
198
|
+
|
199
|
+
Here we have a list with three items. The first item uses the default
|
200
|
+
tag style, the second uses erb style as defined by the Set Delimiter
|
201
|
+
tag, and the third returns to the default style after yet another Set
|
202
|
+
Delimiter declaration.
|
203
|
+
|
204
|
+
According to [ctemplates][3], this "is useful for languages like TeX, where
|
205
|
+
double-braces may occur in the text and are awkward to use for
|
206
|
+
markup."
|
207
|
+
|
208
|
+
Custom delimiters may not contain whitespace or the equals sign.
|
209
|
+
|
210
|
+
|
168
211
|
Dict-Style Views
|
169
212
|
----------------
|
170
213
|
|
@@ -206,11 +249,11 @@ follows the classic Ruby naming convention.
|
|
206
249
|
|
207
250
|
TemplatePartial => ./template_partial.html
|
208
251
|
|
209
|
-
You can set the search path using `Mustache.
|
252
|
+
You can set the search path using `Mustache.template_path`. It can be set on a
|
210
253
|
class by class basis:
|
211
254
|
|
212
255
|
class Simple < Mustache
|
213
|
-
self.
|
256
|
+
self.template_path = File.dirname(__FILE__)
|
214
257
|
... etc ...
|
215
258
|
end
|
216
259
|
|
@@ -222,11 +265,18 @@ If you want to just change what template is used you can set
|
|
222
265
|
|
223
266
|
Simple.template_file = './blah.html'
|
224
267
|
|
225
|
-
|
268
|
+
Mustache also allows you to define the extension it'll use.
|
269
|
+
|
270
|
+
Simple.template_extension = 'xml'
|
271
|
+
|
272
|
+
Given all other defaults, the above line will cause Mustache to look
|
273
|
+
for './blah.xml'
|
274
|
+
|
275
|
+
Feel free to set the template directly:
|
226
276
|
|
227
277
|
Simple.template = 'Hi {{person}}!'
|
228
278
|
|
229
|
-
|
279
|
+
Or set a different template for a single instance:
|
230
280
|
|
231
281
|
Simple.new.template = 'Hi {{person}}!'
|
232
282
|
|
@@ -347,3 +397,4 @@ Meta
|
|
347
397
|
|
348
398
|
[1]: http://code.google.com/p/google-ctemplate/
|
349
399
|
[2]: http://www.ivan.fomichev.name/2008/05/erlang-template-engine-prototype.html
|
400
|
+
[3]: http://google-ctemplate.googlecode.com/svn/trunk/doc/howto.html
|
data/Rakefile
CHANGED
@@ -47,3 +47,16 @@ task :publish => [ :gemspec, :build ] do
|
|
47
47
|
system "git clean -fd"
|
48
48
|
exec "rake pages"
|
49
49
|
end
|
50
|
+
|
51
|
+
desc "Install the edge gem"
|
52
|
+
task :install_edge => [ :dev_version, :gemspec, :build ] do
|
53
|
+
exec "gem install pkg/mustache-#{Mustache::Version}.gem"
|
54
|
+
end
|
55
|
+
|
56
|
+
# Sets the current Mustache version to the current dev version
|
57
|
+
task :dev_version do
|
58
|
+
$LOAD_PATH.unshift 'lib/mustache'
|
59
|
+
require 'mustache/version'
|
60
|
+
version = Mustache::Version + '.' + Time.now.to_i.to_s
|
61
|
+
Mustache.const_set(:Version, version)
|
62
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
2
|
+
require 'mustache'
|
3
|
+
|
4
|
+
class Delimiters < Mustache
|
5
|
+
self.path = File.dirname(__FILE__)
|
6
|
+
|
7
|
+
def first
|
8
|
+
"It worked the first time."
|
9
|
+
end
|
10
|
+
|
11
|
+
def second
|
12
|
+
"And it worked the second time."
|
13
|
+
end
|
14
|
+
|
15
|
+
def third
|
16
|
+
"Then, surprisingly, it worked the third time."
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
if $0 == __FILE__
|
21
|
+
puts Delimiters.to_html
|
22
|
+
end
|
data/lib/mustache.rb
CHANGED
@@ -123,7 +123,18 @@ class Mustache
|
|
123
123
|
end
|
124
124
|
|
125
125
|
def self.template=(template)
|
126
|
-
@template = template
|
126
|
+
@template = templateify(template)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Has this template already been compiled? Compilation is somewhat
|
130
|
+
# expensive so it may be useful to check this before attempting it.
|
131
|
+
def self.compiled?
|
132
|
+
@template.is_a? Template
|
133
|
+
end
|
134
|
+
|
135
|
+
# Has this instance or its class already compiled a template?
|
136
|
+
def compiled?
|
137
|
+
(@template && @template.is_a?(Template)) || self.class.compiled?
|
127
138
|
end
|
128
139
|
|
129
140
|
# template_partial => TemplatePartial
|
data/lib/mustache/sinatra.rb
CHANGED
@@ -67,6 +67,9 @@ class Mustache
|
|
67
67
|
require "#{file}".chomp('.rb')
|
68
68
|
klass = namespace::Views.const_get(name)
|
69
69
|
|
70
|
+
# compile and cache the template
|
71
|
+
klass.template = data
|
72
|
+
|
70
73
|
else
|
71
74
|
# Still nothing. Use the stache.
|
72
75
|
klass = Mustache
|
@@ -90,7 +93,7 @@ class Mustache
|
|
90
93
|
# lets us use {{yield}} in layout.html to render the actual page.
|
91
94
|
instance[:yield] = block.call if block
|
92
95
|
|
93
|
-
instance.template = data
|
96
|
+
instance.template = data unless instance.compiled?
|
94
97
|
instance.to_html
|
95
98
|
end
|
96
99
|
end
|
data/lib/mustache/template.rb
CHANGED
@@ -47,15 +47,30 @@ class Mustache
|
|
47
47
|
# If enumerable, the return value is iterated over (a `for` loop).
|
48
48
|
def compile_sections(src)
|
49
49
|
res = ""
|
50
|
-
while src =~
|
50
|
+
while src =~ /#{otag}\#(.+)#{ctag}\s*(.+)#{otag}\/\1#{ctag}\s*/m
|
51
51
|
# $` = The string to the left of the last successful match
|
52
52
|
res << compile_tags($`)
|
53
53
|
name = $1.strip.to_sym.inspect
|
54
54
|
code = compile($2)
|
55
55
|
ctxtmp = "ctx#{tmpid}"
|
56
|
-
res << ev(
|
57
|
-
|
58
|
-
|
56
|
+
res << ev(<<-compiled)
|
57
|
+
if v = ctx[#{name}]
|
58
|
+
v = [v] if v.is_a?(Hash) # shortcut when passed a single hash
|
59
|
+
if v.respond_to?(:each)
|
60
|
+
#{ctxtmp} = ctx.dup
|
61
|
+
begin
|
62
|
+
r = v.map { |h| ctx.update(h); #{code} }.join
|
63
|
+
rescue TypeError => e
|
64
|
+
raise TypeError,
|
65
|
+
"All elements in {{#{name.to_s[1..-1]}}} are not hashes!"
|
66
|
+
end
|
67
|
+
ctx.replace(#{ctxtmp})
|
68
|
+
r
|
69
|
+
else
|
70
|
+
#{code}
|
71
|
+
end
|
72
|
+
end
|
73
|
+
compiled
|
59
74
|
# $' = The string to the right of the last successful match
|
60
75
|
src = $'
|
61
76
|
end
|
@@ -70,11 +85,13 @@ class Mustache
|
|
70
85
|
# 4. Partial tags - {{< partial_name }}
|
71
86
|
def compile_tags(src)
|
72
87
|
res = ""
|
73
|
-
while src =~
|
88
|
+
while src =~ /#{otag}(=|!|<|\{)?([^\/#]+?)\1?#{ctag}+/
|
74
89
|
res << str($`)
|
75
90
|
case $1
|
76
91
|
when '!'
|
77
92
|
# ignore comments
|
93
|
+
when '='
|
94
|
+
self.otag, self.ctag = $2.strip.split(' ', 2)
|
78
95
|
when '<'
|
79
96
|
res << compile_partial($2.strip)
|
80
97
|
when '{'
|
@@ -108,6 +125,24 @@ class Mustache
|
|
108
125
|
s.inspect[1..-2]
|
109
126
|
end
|
110
127
|
|
128
|
+
# {{ - opening tag delimiter
|
129
|
+
def otag
|
130
|
+
@otag ||= Regexp.escape('{{')
|
131
|
+
end
|
132
|
+
|
133
|
+
def otag=(tag)
|
134
|
+
@otag = Regexp.escape(tag)
|
135
|
+
end
|
136
|
+
|
137
|
+
# }} - closing tag delimiter
|
138
|
+
def ctag
|
139
|
+
@ctag ||= Regexp.escape('}}')
|
140
|
+
end
|
141
|
+
|
142
|
+
def ctag=(tag)
|
143
|
+
@ctag = Regexp.escape(tag)
|
144
|
+
end
|
145
|
+
|
111
146
|
# {{}} - an escaped tag
|
112
147
|
def etag(s)
|
113
148
|
ev("CGI.escapeHTML(ctx[#{s.strip.to_sym.inspect}].to_s)")
|
data/lib/mustache/version.rb
CHANGED
data/test/mustache_test.rb
CHANGED
@@ -9,6 +9,7 @@ require 'escaped'
|
|
9
9
|
require 'unescaped'
|
10
10
|
require 'comments'
|
11
11
|
require 'passenger'
|
12
|
+
require 'delimiters'
|
12
13
|
|
13
14
|
class MustacheTest < Test::Unit::TestCase
|
14
15
|
def test_passenger
|
@@ -24,12 +25,23 @@ end_passenger
|
|
24
25
|
def test_complex_view
|
25
26
|
assert_equal <<-end_complex, ComplexView.render
|
26
27
|
<h1>Colors</h1>
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
<ul>
|
29
|
+
<li><strong>red</strong></li>
|
30
|
+
<li><a href="#Green">green</a></li>
|
31
|
+
<li><a href="#Blue">blue</a></li>
|
32
|
+
</ul>
|
32
33
|
end_complex
|
34
|
+
|
35
|
+
# TODO: Preserve indentation
|
36
|
+
# http://github.com/defunkt/mustache/issues#issue/2
|
37
|
+
# assert_equal <<-end_complex, ComplexView.render
|
38
|
+
# <h1>Colors</h1>
|
39
|
+
# <ul>
|
40
|
+
# <li><strong>red</strong></li>
|
41
|
+
# <li><a href="#Green">green</a></li>
|
42
|
+
# <li><a href="#Blue">blue</a></li>
|
43
|
+
# </ul>
|
44
|
+
# end_complex
|
33
45
|
end
|
34
46
|
|
35
47
|
def test_single_line_sections
|
@@ -131,6 +143,18 @@ Welcome
|
|
131
143
|
end_partial
|
132
144
|
end
|
133
145
|
|
146
|
+
|
147
|
+
def test_delimiters
|
148
|
+
assert_equal <<-end_partial, Delimiters.render
|
149
|
+
|
150
|
+
* It worked the first time.
|
151
|
+
|
152
|
+
* And it worked the second time.
|
153
|
+
|
154
|
+
* Then, surprisingly, it worked the third time.
|
155
|
+
end_partial
|
156
|
+
end
|
157
|
+
|
134
158
|
def test_comments
|
135
159
|
assert_equal "<h1>A Comedy of Errors</h1>\n", Comments.render
|
136
160
|
end
|
@@ -182,4 +206,59 @@ data
|
|
182
206
|
:deploy_to => '/var/www/example.com' )
|
183
207
|
end
|
184
208
|
|
209
|
+
def test_reports_type_errors_in_sections
|
210
|
+
instance = Mustache.new
|
211
|
+
instance[:list] = [ :item, 1234 ]
|
212
|
+
instance.template = '{{#list}} <li>{{item}}</li> {{/list}}'
|
213
|
+
|
214
|
+
assert_raise TypeError do
|
215
|
+
instance.render
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def test_enumerable_sections_accept_a_hash_as_a_context
|
220
|
+
instance = Mustache.new
|
221
|
+
instance[:list] = { :item => 1234 }
|
222
|
+
instance.template = '{{#list}} <li>{{item}}</li> {{/list}}'
|
223
|
+
|
224
|
+
assert_equal '<li>1234</li>', instance.render.strip
|
225
|
+
end
|
226
|
+
|
227
|
+
def test_knows_when_its_been_compiled_when_set_with_string
|
228
|
+
klass = Class.new(Mustache)
|
229
|
+
|
230
|
+
assert ! klass.compiled?
|
231
|
+
klass.template = 'Hi, {{person}}!'
|
232
|
+
assert klass.compiled?
|
233
|
+
end
|
234
|
+
|
235
|
+
def test_knows_when_its_been_compiled_when_using_a_file_template
|
236
|
+
klass = Class.new(Simple)
|
237
|
+
klass.template_file = File.dirname(__FILE__) + '/../examples/simple.html'
|
238
|
+
|
239
|
+
assert ! klass.compiled?
|
240
|
+
klass.render
|
241
|
+
assert klass.compiled?
|
242
|
+
end
|
243
|
+
|
244
|
+
def test_an_instance_knows_when_its_class_is_compiled
|
245
|
+
instance = Simple.new
|
246
|
+
|
247
|
+
assert ! Simple.compiled?
|
248
|
+
assert ! instance.compiled?
|
249
|
+
|
250
|
+
Simple.render
|
251
|
+
|
252
|
+
assert Simple.compiled?
|
253
|
+
assert instance.compiled?
|
254
|
+
end
|
255
|
+
|
256
|
+
def test_knows_when_its_been_compiled_at_the_instance_level
|
257
|
+
klass = Class.new(Mustache)
|
258
|
+
instance = klass.new
|
259
|
+
|
260
|
+
assert ! instance.compiled?
|
261
|
+
instance.template = 'Hi, {{person}}!'
|
262
|
+
assert instance.compiled?
|
263
|
+
end
|
185
264
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mustache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Wanstrath
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-10-
|
12
|
+
date: 2009-10-14 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -38,6 +38,8 @@ files:
|
|
38
38
|
- examples/comments.rb
|
39
39
|
- examples/complex_view.html
|
40
40
|
- examples/complex_view.rb
|
41
|
+
- examples/delimiters.html
|
42
|
+
- examples/delimiters.rb
|
41
43
|
- examples/escaped.html
|
42
44
|
- examples/escaped.rb
|
43
45
|
- examples/inner_partial.html
|
@@ -91,6 +93,7 @@ test_files:
|
|
91
93
|
- test/mustache_test.rb
|
92
94
|
- examples/comments.rb
|
93
95
|
- examples/complex_view.rb
|
96
|
+
- examples/delimiters.rb
|
94
97
|
- examples/escaped.rb
|
95
98
|
- examples/passenger.rb
|
96
99
|
- examples/simple.rb
|