mustache 0.11.2 → 0.12.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/README.md +14 -4
- data/lib/mustache.rb +25 -3
- data/lib/mustache/context.rb +14 -2
- data/lib/mustache/generator.rb +20 -8
- data/lib/mustache/parser.rb +39 -11
- data/lib/mustache/sinatra.rb +10 -9
- data/lib/mustache/template.rb +1 -2
- data/lib/mustache/version.rb +1 -1
- data/test/fixtures/crazy_recursive.mustache +1 -1
- data/test/fixtures/lambda.mustache +6 -2
- data/test/fixtures/lambda.rb +5 -1
- data/test/fixtures/node.mustache +2 -2
- data/test/fixtures/partial_with_module.mustache +1 -0
- data/test/fixtures/utf8_partial.mustache +1 -1
- data/test/mustache_test.rb +105 -41
- data/test/parser_test.rb +10 -5
- data/test/partial_test.rb +29 -29
- data/test/spec_test.rb +68 -0
- data/test/template_test.rb +20 -0
- metadata +12 -5
data/README.md
CHANGED
@@ -208,9 +208,9 @@ you want to use in all your views? No problem.
|
|
208
208
|
This is just Ruby, after all.
|
209
209
|
|
210
210
|
module ViewHelpers
|
211
|
-
def gravatar
|
212
|
-
gravatar_id = Digest::MD5.hexdigest(email.to_s.strip.downcase)
|
213
|
-
gravatar_for_id(gravatar_id
|
211
|
+
def gravatar
|
212
|
+
gravatar_id = Digest::MD5.hexdigest(self[:email].to_s.strip.downcase)
|
213
|
+
gravatar_for_id(gravatar_id)
|
214
214
|
end
|
215
215
|
|
216
216
|
def gravatar_for_id(gid, size = 30)
|
@@ -242,6 +242,10 @@ Then just include it:
|
|
242
242
|
def in_ca
|
243
243
|
true
|
244
244
|
end
|
245
|
+
|
246
|
+
def users
|
247
|
+
User.all
|
248
|
+
end
|
245
249
|
end
|
246
250
|
|
247
251
|
Great, but what about that `@ssl` ivar in `gravatar_host`? There are
|
@@ -265,7 +269,13 @@ Now:
|
|
265
269
|
|
266
270
|
Simple.new(request.ssl?).render
|
267
271
|
|
268
|
-
|
272
|
+
Finally, our template might look like this:
|
273
|
+
|
274
|
+
<ul>
|
275
|
+
{{# users}}
|
276
|
+
<li><img src="{{ gravatar }}"> {{ login }}</li>
|
277
|
+
{{/ users}}
|
278
|
+
</ul>
|
269
279
|
|
270
280
|
|
271
281
|
Sinatra
|
data/lib/mustache.rb
CHANGED
@@ -70,7 +70,10 @@ require 'mustache/context'
|
|
70
70
|
# for files containing view classes when using the `view_class` method.
|
71
71
|
#
|
72
72
|
class Mustache
|
73
|
-
#
|
73
|
+
# Instantiates an instance of this class and calls `render` with
|
74
|
+
# the passed args.
|
75
|
+
#
|
76
|
+
# Returns a rendered String version of a template
|
74
77
|
def self.render(*args)
|
75
78
|
new.render(*args)
|
76
79
|
end
|
@@ -288,7 +291,7 @@ class Mustache
|
|
288
291
|
end
|
289
292
|
end
|
290
293
|
|
291
|
-
# Return the value of the configuration setting on the superclass, or return
|
294
|
+
# Return the value of the configuration setting on the superclass, or return
|
292
295
|
# the default.
|
293
296
|
#
|
294
297
|
# attr_name - Symbol name of the attribute. It should match the instance variable.
|
@@ -341,8 +344,27 @@ class Mustache
|
|
341
344
|
|
342
345
|
# Parses our fancy pants template file and returns normal file with
|
343
346
|
# all special {{tags}} and {{#sections}}replaced{{/sections}}.
|
347
|
+
#
|
348
|
+
# data - A String template or a Hash context. If a Hash is given,
|
349
|
+
# we'll try to figure out the template from the class.
|
350
|
+
# ctx - A Hash context if `data` is a String template.
|
351
|
+
#
|
352
|
+
# Examples
|
353
|
+
#
|
354
|
+
# @view.render("Hi {{thing}}!", :thing => :world)
|
355
|
+
#
|
356
|
+
# View.template = "Hi {{thing}}!"
|
357
|
+
# @view = View.new
|
358
|
+
# @view.render(:thing => :world)
|
359
|
+
#
|
360
|
+
# Returns a rendered String version of a template
|
344
361
|
def render(data = template, ctx = {})
|
345
|
-
|
362
|
+
if data.is_a? Hash
|
363
|
+
ctx = data
|
364
|
+
tpl = templateify(template)
|
365
|
+
else
|
366
|
+
tpl = templateify(data)
|
367
|
+
end
|
346
368
|
|
347
369
|
return tpl.render(context) if ctx == {}
|
348
370
|
|
data/lib/mustache/context.rb
CHANGED
@@ -12,6 +12,8 @@ class Mustache
|
|
12
12
|
# A Context represents the context which a Mustache template is
|
13
13
|
# executed within. All Mustache tags reference keys in the Context.
|
14
14
|
class Context
|
15
|
+
attr_accessor :frame, :key
|
16
|
+
|
15
17
|
# Expect to be passed an instance of `Mustache`.
|
16
18
|
def initialize(mustache)
|
17
19
|
@stack = [mustache]
|
@@ -23,12 +25,15 @@ class Mustache
|
|
23
25
|
# If the Mustache view handling the rendering (e.g. the view
|
24
26
|
# representing your profile page or some other template) responds
|
25
27
|
# to `partial`, we call it and render the result.
|
26
|
-
def partial(name)
|
28
|
+
def partial(name, indentation = '')
|
27
29
|
# Look for the first Mustache in the stack.
|
28
30
|
mustache = mustache_in_stack
|
29
31
|
|
30
32
|
# Call its `partial` method and render the result.
|
31
|
-
mustache.render(mustache.partial(name), self)
|
33
|
+
result = mustache.render(mustache.partial(name), self)
|
34
|
+
|
35
|
+
# Indent the rendered partial by the given indentation.
|
36
|
+
result.gsub(/^/, indentation)
|
32
37
|
end
|
33
38
|
|
34
39
|
# Find the first Mustache in the stack. If we're being rendered
|
@@ -82,9 +87,12 @@ class Mustache
|
|
82
87
|
# If no second parameter is passed (or raise_on_context_miss is
|
83
88
|
# set to true), will raise a ContextMiss exception on miss.
|
84
89
|
def fetch(name, default = :__raise)
|
90
|
+
@key = name
|
91
|
+
|
85
92
|
@stack.each do |frame|
|
86
93
|
# Prevent infinite recursion.
|
87
94
|
next if frame == self
|
95
|
+
@frame = frame
|
88
96
|
|
89
97
|
# Is this frame a hash?
|
90
98
|
hash = frame.respond_to?(:has_key?)
|
@@ -92,12 +100,16 @@ class Mustache
|
|
92
100
|
if hash && frame.has_key?(name)
|
93
101
|
return frame[name]
|
94
102
|
elsif hash && frame.has_key?(name.to_s)
|
103
|
+
@key = name.to_s
|
95
104
|
return frame[name.to_s]
|
96
105
|
elsif !hash && frame.respond_to?(name)
|
106
|
+
@frame = nil
|
97
107
|
return frame.__send__(name)
|
98
108
|
end
|
99
109
|
end
|
100
110
|
|
111
|
+
@frame = @key = nil
|
112
|
+
|
101
113
|
if default == :__raise || mustache_in_stack.raise_on_context_miss?
|
102
114
|
raise ContextMiss.new("Can't find #{name} in #{@stack.inspect}")
|
103
115
|
else
|
data/lib/mustache/generator.rb
CHANGED
@@ -90,7 +90,7 @@ class Mustache
|
|
90
90
|
|
91
91
|
# Callback fired when the compiler finds a section token. We're
|
92
92
|
# passed the section name and the array of tokens.
|
93
|
-
def on_section(name, content)
|
93
|
+
def on_section(name, content, raw)
|
94
94
|
# Convert the tokenized content of this section into a Ruby
|
95
95
|
# string we can use.
|
96
96
|
code = compile(content)
|
@@ -102,9 +102,11 @@ class Mustache
|
|
102
102
|
if v == true
|
103
103
|
#{code}
|
104
104
|
elsif v.is_a?(Proc)
|
105
|
-
v.call(#{
|
105
|
+
Mustache::Template.new(v.call(#{raw.inspect}).to_s).render(ctx.dup)
|
106
106
|
else
|
107
|
-
|
107
|
+
# Shortcut when passed non-array
|
108
|
+
v = [v] if v.respond_to?(:has_key?) || !v.respond_to?(:map)
|
109
|
+
|
108
110
|
v.map { |h| ctx.push(h); r = #{code}; ctx.pop; r }.join
|
109
111
|
end
|
110
112
|
end
|
@@ -113,7 +115,7 @@ class Mustache
|
|
113
115
|
|
114
116
|
# Fired when we find an inverted section. Just like `on_section`,
|
115
117
|
# we're passed the inverted section name and the array of tokens.
|
116
|
-
def on_inverted_section(name, content)
|
118
|
+
def on_inverted_section(name, content, raw)
|
117
119
|
# Convert the tokenized content of this section into a Ruby
|
118
120
|
# string we can use.
|
119
121
|
code = compile(content)
|
@@ -131,18 +133,28 @@ class Mustache
|
|
131
133
|
# Fired when the compiler finds a partial. We want to return code
|
132
134
|
# which calls a partial at runtime instead of expanding and
|
133
135
|
# including the partial's body to allow for recursive partials.
|
134
|
-
def on_partial(name)
|
135
|
-
ev("ctx.partial(#{name.to_sym.inspect})")
|
136
|
+
def on_partial(name, indentation)
|
137
|
+
ev("ctx.partial(#{name.to_sym.inspect}, #{indentation.inspect})")
|
136
138
|
end
|
137
139
|
|
138
140
|
# An unescaped tag.
|
139
141
|
def on_utag(name)
|
140
|
-
ev(
|
142
|
+
ev(<<-compiled)
|
143
|
+
v = ctx[#{name.to_sym.inspect}]
|
144
|
+
v = Mustache::Template.new(v.call.to_s).render(ctx.dup) if v.is_a?(Proc)
|
145
|
+
ctx.frame[ctx.key] = v if ctx.frame.is_a?(Hash)
|
146
|
+
v.to_s
|
147
|
+
compiled
|
141
148
|
end
|
142
149
|
|
143
150
|
# An escaped tag.
|
144
151
|
def on_etag(name)
|
145
|
-
ev(
|
152
|
+
ev(<<-compiled)
|
153
|
+
v = ctx[#{name.to_sym.inspect}]
|
154
|
+
v = Mustache::Template.new(v.call.to_s).render(ctx.dup) if v.is_a?(Proc)
|
155
|
+
ctx.frame[ctx.key] = v if ctx.frame.is_a?(Hash)
|
156
|
+
CGI.escapeHTML(v.to_s)
|
157
|
+
compiled
|
146
158
|
end
|
147
159
|
|
148
160
|
# An interpolation-friendly version of a string, for use within a
|
data/lib/mustache/parser.rb
CHANGED
@@ -29,7 +29,7 @@ class Mustache
|
|
29
29
|
class SyntaxError < StandardError
|
30
30
|
def initialize(message, position)
|
31
31
|
@message = message
|
32
|
-
@lineno, @column, @line = position
|
32
|
+
@lineno, @column, @line, _ = position
|
33
33
|
@stripped_line = @line.strip
|
34
34
|
@stripped_column = @column - (@line.size - @line.lstrip.size)
|
35
35
|
end
|
@@ -44,8 +44,10 @@ EOF
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
# After these types of tags, all whitespace
|
48
|
-
|
47
|
+
# After these types of tags, all whitespace until the end of the line will
|
48
|
+
# be skipped if they are the first (and only) non-whitespace content on
|
49
|
+
# the line.
|
50
|
+
SKIP_WHITESPACE = [ '#', '^', '/', '<', '>', '=', '!' ]
|
49
51
|
|
50
52
|
# The content allowed in a tag name.
|
51
53
|
ALLOWED_CONTENT = /(\w|[?!\/-])*/
|
@@ -104,7 +106,18 @@ EOF
|
|
104
106
|
# Find {{mustaches}} and add them to the @result array.
|
105
107
|
def scan_tags
|
106
108
|
# Scan until we hit an opening delimiter.
|
107
|
-
|
109
|
+
start_of_line = @scanner.beginning_of_line?
|
110
|
+
pre_match_position = @scanner.pos
|
111
|
+
|
112
|
+
return unless x = @scanner.scan(/([ \t]*)?#{Regexp.escape(otag)}/)
|
113
|
+
padding = @scanner[1] || ''
|
114
|
+
|
115
|
+
# Don't touch the preceding whitespace unless we're matching the start
|
116
|
+
# of a new line.
|
117
|
+
unless start_of_line
|
118
|
+
@result << [:static, padding] unless padding.empty?
|
119
|
+
padding = ''
|
120
|
+
end
|
108
121
|
|
109
122
|
# Since {{= rewrites ctag, we store the ctag which should be used
|
110
123
|
# when parsing this specific tag.
|
@@ -138,7 +151,8 @@ EOF
|
|
138
151
|
@result = block
|
139
152
|
when '/'
|
140
153
|
section, pos, result = @sections.pop
|
141
|
-
|
154
|
+
raw = @scanner.pre_match[pos[3]...pre_match_position] + padding
|
155
|
+
(@result = result).last << raw
|
142
156
|
|
143
157
|
if section.nil?
|
144
158
|
error "Closing unopened #{content.inspect}"
|
@@ -150,7 +164,7 @@ EOF
|
|
150
164
|
when '='
|
151
165
|
self.otag, self.ctag = content.split(' ', 2)
|
152
166
|
when '>', '<'
|
153
|
-
@result << [:mustache, :partial, content]
|
167
|
+
@result << [:mustache, :partial, content, padding]
|
154
168
|
when '{', '&'
|
155
169
|
# The closing } in unescaped tags is just a hack for
|
156
170
|
# aesthetics.
|
@@ -170,24 +184,38 @@ EOF
|
|
170
184
|
error "Unclosed tag"
|
171
185
|
end
|
172
186
|
|
173
|
-
#
|
174
|
-
|
187
|
+
# If this tag was the only non-whitespace content on this line, strip
|
188
|
+
# the remaining whitespace. If not, but we've been hanging on to padding
|
189
|
+
# from the beginning of the line, re-insert the padding as static text.
|
190
|
+
if start_of_line
|
191
|
+
if SKIP_WHITESPACE.include?(type)
|
192
|
+
@scanner.skip(/[ \t]*\n/)
|
193
|
+
else
|
194
|
+
@result.insert(-2, [:static, padding]) unless padding.empty?
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
return unless @result == [:multi]
|
199
|
+
|
200
|
+
# Store off the current scanner position now that we've closed the tag
|
201
|
+
# and consumed any irrelevant whitespace.
|
202
|
+
@sections.last[1] << @scanner.pos unless @sections.empty?
|
175
203
|
end
|
176
204
|
|
177
205
|
# Try to find static text, e.g. raw HTML with no {{mustaches}}.
|
178
206
|
def scan_text
|
179
|
-
text = scan_until_exclusive(
|
207
|
+
text = scan_until_exclusive(/(^[ \t]*)?#{Regexp.escape(otag)}/)
|
180
208
|
|
181
209
|
if text.nil?
|
182
210
|
# Couldn't find any otag, which means the rest is just static text.
|
183
211
|
text = @scanner.rest
|
184
212
|
# Mark as done.
|
185
|
-
@scanner.
|
213
|
+
@scanner.terminate
|
186
214
|
end
|
187
215
|
|
188
216
|
text.force_encoding(@encoding) if @encoding
|
189
217
|
|
190
|
-
@result << [:static, text]
|
218
|
+
@result << [:static, text] unless text.empty?
|
191
219
|
end
|
192
220
|
|
193
221
|
# Scans the string until the pattern is matched. Returns the substring
|
data/lib/mustache/sinatra.rb
CHANGED
@@ -51,11 +51,6 @@ class Mustache
|
|
51
51
|
options = settings.send(:mustache).merge(options)
|
52
52
|
end
|
53
53
|
|
54
|
-
# Find and cache the view class we want. This ensures the
|
55
|
-
# compiled template is cached, too - no looking up and
|
56
|
-
# compiling templates on each page load.
|
57
|
-
klass = mustache_class(template, options)
|
58
|
-
|
59
54
|
# If they aren't explicitly disabling layouts, try to find
|
60
55
|
# one.
|
61
56
|
if options[:layout] != false
|
@@ -76,12 +71,18 @@ class Mustache
|
|
76
71
|
layout = layout.new
|
77
72
|
end
|
78
73
|
|
79
|
-
# Does the view subclass the layout? If so we'll use the
|
80
|
-
# view to render the layout so you can override layout
|
81
|
-
# methods in your view - tricky.
|
82
|
-
view_subclasses_layout = klass < layout.class if layout
|
83
74
|
end
|
84
75
|
|
76
|
+
# Find and cache the view class we want. This ensures the
|
77
|
+
# compiled template is cached, too - no looking up and
|
78
|
+
# compiling templates on each page load.
|
79
|
+
klass = mustache_class(template, options)
|
80
|
+
|
81
|
+
# Does the view subclass the layout? If so we'll use the
|
82
|
+
# view to render the layout so you can override layout
|
83
|
+
# methods in your view - tricky.
|
84
|
+
view_subclasses_layout = klass < layout.class if layout
|
85
|
+
|
85
86
|
# Create a new instance for playing with.
|
86
87
|
instance = klass.new
|
87
88
|
|
data/lib/mustache/template.rb
CHANGED
@@ -21,7 +21,6 @@ class Mustache
|
|
21
21
|
# path, which it uses to find partials.
|
22
22
|
def initialize(source)
|
23
23
|
@source = source
|
24
|
-
@tmpid = 0
|
25
24
|
end
|
26
25
|
|
27
26
|
# Renders the `@source` Mustache template using the given
|
@@ -47,7 +46,7 @@ class Mustache
|
|
47
46
|
# Does the dirty work of transforming a Mustache template into an
|
48
47
|
# interpolation-friendly Ruby string.
|
49
48
|
def compile(src = @source)
|
50
|
-
Generator.new.compile(tokens)
|
49
|
+
Generator.new.compile(tokens(src))
|
51
50
|
end
|
52
51
|
alias_method :to_s, :compile
|
53
52
|
|
data/lib/mustache/version.rb
CHANGED
data/test/fixtures/lambda.rb
CHANGED
@@ -12,7 +12,7 @@ class Lambda < Mustache
|
|
12
12
|
@cached = nil
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
15
|
+
def rendered
|
16
16
|
lambda do |text|
|
17
17
|
return @cached if @cached
|
18
18
|
|
@@ -20,6 +20,10 @@ class Lambda < Mustache
|
|
20
20
|
@cached = render(text)
|
21
21
|
end
|
22
22
|
end
|
23
|
+
|
24
|
+
def not_rendered
|
25
|
+
lambda { |text| "{{= | =}}#{text}" }
|
26
|
+
end
|
23
27
|
end
|
24
28
|
|
25
29
|
if $0 == __FILE__
|
data/test/fixtures/node.mustache
CHANGED
@@ -1 +1 @@
|
|
1
|
-
<h2>中文又来啦</h2>
|
1
|
+
<h2>中文又来啦</h2>
|
data/test/mustache_test.rb
CHANGED
@@ -3,6 +3,17 @@ $LOAD_PATH.unshift File.dirname(__FILE__)
|
|
3
3
|
require 'helper'
|
4
4
|
|
5
5
|
class MustacheTest < Test::Unit::TestCase
|
6
|
+
def test_instance_render
|
7
|
+
klass = Class.new(Mustache)
|
8
|
+
klass.template = "Hi {{thing}}!"
|
9
|
+
assert_equal "Hi world!", klass.render(:thing => :world)
|
10
|
+
assert_equal "Nice.", klass.render("{{compliment}}.", :compliment => "Nice")
|
11
|
+
assert_equal <<-end_simple, Simple.new.render(:name => "yo", :in_ca => false)
|
12
|
+
Hello yo
|
13
|
+
You have just won $10000!
|
14
|
+
end_simple
|
15
|
+
end
|
16
|
+
|
6
17
|
def test_passenger
|
7
18
|
assert_equal <<-end_passenger, Passenger.to_text
|
8
19
|
<VirtualHost *>
|
@@ -16,22 +27,24 @@ end_passenger
|
|
16
27
|
def test_complex_view
|
17
28
|
assert_equal <<-end_complex, ComplexView.render
|
18
29
|
<h1>Colors</h1>
|
19
|
-
<ul>
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
30
|
+
<ul>
|
31
|
+
<li><strong>red</strong></li>
|
32
|
+
<li><a href="#Green">green</a></li>
|
33
|
+
<li><a href="#Blue">blue</a></li>
|
34
|
+
</ul>
|
35
|
+
|
24
36
|
end_complex
|
25
37
|
end
|
26
38
|
|
27
39
|
def test_nested_objects
|
28
40
|
assert_equal <<-end_complex, NestedObjects.render
|
29
41
|
<h1>Colors</h1>
|
30
|
-
<ul>
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
42
|
+
<ul>
|
43
|
+
<li><strong>red</strong></li>
|
44
|
+
<li><a href="#Green">green</a></li>
|
45
|
+
<li><a href="#Blue">blue</a></li>
|
46
|
+
</ul>
|
47
|
+
|
35
48
|
end_complex
|
36
49
|
end
|
37
50
|
|
@@ -41,9 +54,7 @@ end_complex
|
|
41
54
|
instance = Mustache.new
|
42
55
|
instance.template = html
|
43
56
|
instance[:no_flash] = true
|
44
|
-
assert_equal
|
45
|
-
<p class="flash-notice" style="display: none;">
|
46
|
-
rendered
|
57
|
+
assert_equal %Q'<p class="flash-notice" style="display: none;">', instance.render
|
47
58
|
end
|
48
59
|
|
49
60
|
def test_two_line_sections
|
@@ -52,9 +63,7 @@ rendered
|
|
52
63
|
instance = Mustache.new
|
53
64
|
instance.template = html
|
54
65
|
instance[:no_flash] = true
|
55
|
-
assert_equal
|
56
|
-
<p class="flash-notice" style="display: none;"\n>
|
57
|
-
rendered
|
66
|
+
assert_equal %Q'<p class="flash-notice" style="display: none;"\n>', instance.render
|
58
67
|
end
|
59
68
|
|
60
69
|
def test_multi_line_sections_preserve_trailing_newline
|
@@ -67,7 +76,7 @@ Howday.
|
|
67
76
|
template
|
68
77
|
|
69
78
|
view[:something] = true
|
70
|
-
assert_equal <<-rendered
|
79
|
+
assert_equal <<-rendered, view.render
|
71
80
|
yay
|
72
81
|
Howday.
|
73
82
|
rendered
|
@@ -78,9 +87,7 @@ rendered
|
|
78
87
|
|
79
88
|
instance = Mustache.new
|
80
89
|
instance.template = html
|
81
|
-
assert_equal
|
82
|
-
<p class="flash-notice" style="display: none;">
|
83
|
-
rendered
|
90
|
+
assert_equal %Q'<p class="flash-notice" style="display: none;">', instance.render
|
84
91
|
end
|
85
92
|
|
86
93
|
def test_simple
|
@@ -131,29 +138,26 @@ end_simple
|
|
131
138
|
|
132
139
|
def test_delimiters
|
133
140
|
assert_equal <<-end_template, Delimiters.render
|
134
|
-
|
135
141
|
* It worked the first time.
|
136
|
-
|
137
142
|
* And it worked the second time.
|
138
143
|
* As well as the third.
|
139
|
-
|
140
144
|
* Then, surprisingly, it worked the final time.
|
141
145
|
end_template
|
142
146
|
end
|
143
147
|
|
144
148
|
def test_double_section
|
145
|
-
assert_equal <<-end_section
|
146
|
-
* first
|
149
|
+
assert_equal <<-end_section, DoubleSection.render
|
150
|
+
* first
|
147
151
|
* second
|
148
|
-
* third
|
152
|
+
* third
|
149
153
|
end_section
|
150
154
|
end
|
151
155
|
|
152
156
|
def test_inverted_section
|
153
|
-
assert_equal <<-end_section
|
154
|
-
* first
|
157
|
+
assert_equal <<-end_section, InvertedSection.render
|
158
|
+
* first
|
155
159
|
* second
|
156
|
-
* third
|
160
|
+
* third
|
157
161
|
end_section
|
158
162
|
end
|
159
163
|
|
@@ -231,7 +235,7 @@ data
|
|
231
235
|
end
|
232
236
|
instance.template = '{{#show}} <li>{{die}}</li> {{/show}} yay'
|
233
237
|
|
234
|
-
assert_equal "yay", instance.render
|
238
|
+
assert_equal " yay", instance.render
|
235
239
|
end
|
236
240
|
|
237
241
|
def test_reports_unclosed_sections
|
@@ -265,7 +269,7 @@ data
|
|
265
269
|
instance[:list] = { :item => 1234 }
|
266
270
|
instance.template = '{{#list}} <li>{{item}}</li> {{/list}}'
|
267
271
|
|
268
|
-
assert_equal '<li>1234</li>', instance.render
|
272
|
+
assert_equal ' <li>1234</li> ', instance.render
|
269
273
|
end
|
270
274
|
|
271
275
|
def test_enumerable_sections_accept_a_string_keyed_hash_as_a_context
|
@@ -273,14 +277,14 @@ data
|
|
273
277
|
instance[:list] = { 'item' => 1234 }
|
274
278
|
instance.template = '{{#list}} <li>{{item}}</li> {{/list}}'
|
275
279
|
|
276
|
-
assert_equal '<li>1234</li>', instance.render
|
280
|
+
assert_equal ' <li>1234</li> ', instance.render
|
277
281
|
end
|
278
282
|
|
279
283
|
def test_not_found_in_context_renders_empty_string
|
280
284
|
instance = Mustache.new
|
281
285
|
instance.template = '{{#list}} <li>{{item}}</li> {{/list}}'
|
282
286
|
|
283
|
-
assert_equal '', instance.render
|
287
|
+
assert_equal '', instance.render
|
284
288
|
end
|
285
289
|
|
286
290
|
def test_not_found_in_nested_context_renders_empty_string
|
@@ -288,7 +292,7 @@ data
|
|
288
292
|
instance[:list] = { :item => 1234 }
|
289
293
|
instance.template = '{{#list}} <li>{{prefix}}{{item}}</li> {{/list}}'
|
290
294
|
|
291
|
-
assert_equal '<li>1234</li>', instance.render
|
295
|
+
assert_equal ' <li>1234</li> ', instance.render
|
292
296
|
end
|
293
297
|
|
294
298
|
def test_not_found_in_context_raises_when_asked_to
|
@@ -297,7 +301,7 @@ data
|
|
297
301
|
instance.template = '{{#list}} <li>{{item}}</li> {{/list}}'
|
298
302
|
|
299
303
|
assert_raises Mustache::ContextMiss do
|
300
|
-
instance.render
|
304
|
+
instance.render
|
301
305
|
end
|
302
306
|
end
|
303
307
|
|
@@ -343,13 +347,13 @@ data
|
|
343
347
|
view = Lambda.new
|
344
348
|
view[:name] = 'Chris'
|
345
349
|
|
346
|
-
assert_equal "Hi Chris.", view.render.chomp
|
350
|
+
assert_equal "Hi Chris.\n\nHi {{name}}.", view.render.chomp
|
347
351
|
assert_equal 1, view.calls
|
348
352
|
|
349
|
-
assert_equal "Hi Chris.", view.render.chomp
|
353
|
+
assert_equal "Hi Chris.\n\nHi {{name}}.", view.render.chomp
|
350
354
|
assert_equal 1, view.calls
|
351
355
|
|
352
|
-
assert_equal "Hi Chris.", view.render.chomp
|
356
|
+
assert_equal "Hi Chris.\n\nHi {{name}}.", view.render.chomp
|
353
357
|
assert_equal 1, view.calls
|
354
358
|
end
|
355
359
|
|
@@ -380,7 +384,7 @@ data
|
|
380
384
|
{{#items}}
|
381
385
|
start
|
382
386
|
{{#items}}
|
383
|
-
|
387
|
+
{{a}}
|
384
388
|
{{/items}}
|
385
389
|
end
|
386
390
|
{{/items}}
|
@@ -414,14 +418,14 @@ expected
|
|
414
418
|
end
|
415
419
|
|
416
420
|
def test_id_with_nested_context
|
417
|
-
html = %(<div>{{id}}</div>\n<div>{{# has_a? }}{{id}}{{/ has_a? }}</div>\n<div>{{# has_b? }}{{id}}{{/ has_b? }}</div
|
421
|
+
html = %(<div>{{id}}</div>\n<div>{{# has_a? }}{{id}}{{/ has_a? }}</div>\n<div>{{# has_b? }}{{id}}{{/ has_b? }}</div>\n)
|
418
422
|
|
419
423
|
instance = Mustache.new
|
420
424
|
instance.template = html
|
421
425
|
instance[:id] = 3
|
422
426
|
instance[:has_a?] = true
|
423
427
|
instance[:has_b?] = true
|
424
|
-
assert_equal <<-rendered
|
428
|
+
assert_equal <<-rendered, instance.render
|
425
429
|
<div>3</div>
|
426
430
|
<div>3</div>
|
427
431
|
<div>3</div>
|
@@ -473,4 +477,64 @@ template
|
|
473
477
|
assert_equal value, tmpl.send(attr)
|
474
478
|
end
|
475
479
|
end
|
480
|
+
def test_array_of_arrays
|
481
|
+
template = <<template
|
482
|
+
{{#items}}
|
483
|
+
start
|
484
|
+
{{#map}}
|
485
|
+
{{a}}
|
486
|
+
{{/map}}
|
487
|
+
end
|
488
|
+
{{/items}}
|
489
|
+
template
|
490
|
+
|
491
|
+
data = {
|
492
|
+
"items" => [
|
493
|
+
[ {"a" => 1}, {"a" => 2}, {"a" => 3} ],
|
494
|
+
[ {"a" => 4}, {"a" => 5}, {"a" => 6} ],
|
495
|
+
[ {"a" => 7}, {"a" => 8}, {"a" => 9} ]
|
496
|
+
]
|
497
|
+
}
|
498
|
+
|
499
|
+
assert_equal <<expected, Mustache.render(template, data)
|
500
|
+
start
|
501
|
+
1
|
502
|
+
2
|
503
|
+
3
|
504
|
+
end
|
505
|
+
start
|
506
|
+
4
|
507
|
+
5
|
508
|
+
6
|
509
|
+
end
|
510
|
+
start
|
511
|
+
7
|
512
|
+
8
|
513
|
+
9
|
514
|
+
end
|
515
|
+
expected
|
516
|
+
end
|
517
|
+
def test_indentation_again
|
518
|
+
template = <<template
|
519
|
+
SELECT
|
520
|
+
{{#cols}}
|
521
|
+
{{name}},
|
522
|
+
{{/cols}}
|
523
|
+
FROM
|
524
|
+
DUMMY1
|
525
|
+
template
|
526
|
+
|
527
|
+
view = Mustache.new
|
528
|
+
view[:cols] = [{:name => 'Name'}, {:name => 'Age'}, {:name => 'Weight'}]
|
529
|
+
view.template = template
|
530
|
+
|
531
|
+
assert_equal <<template, view.render
|
532
|
+
SELECT
|
533
|
+
Name,
|
534
|
+
Age,
|
535
|
+
Weight,
|
536
|
+
FROM
|
537
|
+
DUMMY1
|
538
|
+
template
|
539
|
+
end
|
476
540
|
end
|
data/test/parser_test.rb
CHANGED
@@ -32,22 +32,27 @@ EOF
|
|
32
32
|
:section,
|
33
33
|
"first",
|
34
34
|
[:multi,
|
35
|
-
[:static, "<li><strong>"],
|
35
|
+
[:static, " <li><strong>"],
|
36
36
|
[:mustache, :etag, "name"],
|
37
|
-
[:static, "</strong></li>\n"]]
|
37
|
+
[:static, "</strong></li>\n"]],
|
38
|
+
%Q' <li><strong>{{name}}</strong></li>\n'],
|
38
39
|
[:mustache,
|
39
40
|
:section,
|
40
41
|
"link",
|
41
42
|
[:multi,
|
42
|
-
[:static, "<li><a href=\""],
|
43
|
+
[:static, " <li><a href=\""],
|
43
44
|
[:mustache, :etag, "url"],
|
44
45
|
[:static, "\">"],
|
45
46
|
[:mustache, :etag, "name"],
|
46
|
-
[:static, "</a></li>\n"]]
|
47
|
+
[:static, "</a></li>\n"]],
|
48
|
+
%Q' <li><a href="{{url}}">{{name}}</a></li>\n']],
|
49
|
+
%Q'{{#first}}\n <li><strong>{{name}}</strong></li>\n{{/first}}\n{{#link}}\n <li><a href="{{url}}">{{name}}</a></li>\n{{/link}}\n'],
|
50
|
+
[:static, "\n"],
|
47
51
|
[:mustache,
|
48
52
|
:section,
|
49
53
|
"empty",
|
50
|
-
[:multi, [:static, "<p>The list is empty.</p>\n"]]
|
54
|
+
[:multi, [:static, "<p>The list is empty.</p>\n"]],
|
55
|
+
%Q'<p>The list is empty.</p>\n']]
|
51
56
|
|
52
57
|
assert_equal expected, tokens
|
53
58
|
end
|
data/test/partial_test.rb
CHANGED
@@ -99,36 +99,36 @@ end_partial
|
|
99
99
|
<body>
|
100
100
|
<ul>
|
101
101
|
<li>
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
102
|
+
1
|
103
|
+
<ul>
|
104
|
+
<li>
|
105
|
+
2
|
106
|
+
<ul>
|
107
|
+
<li>
|
108
|
+
3
|
109
|
+
<ul>
|
110
|
+
</ul>
|
111
|
+
</li>
|
112
|
+
</ul>
|
113
|
+
</li>
|
114
|
+
<li>
|
115
|
+
4
|
116
|
+
<ul>
|
117
|
+
<li>
|
118
|
+
5
|
119
|
+
<ul>
|
120
|
+
<li>
|
121
|
+
6
|
122
|
+
<ul>
|
123
|
+
</ul>
|
124
|
+
</li>
|
125
|
+
</ul>
|
126
|
+
</li>
|
127
|
+
</ul>
|
128
|
+
</li>
|
129
|
+
</ul>
|
130
|
+
</li>
|
110
131
|
</ul>
|
111
|
-
</li>
|
112
|
-
</ul>
|
113
|
-
</li>
|
114
|
-
<li>
|
115
|
-
4
|
116
|
-
<ul>
|
117
|
-
<li>
|
118
|
-
5
|
119
|
-
<ul>
|
120
|
-
<li>
|
121
|
-
6
|
122
|
-
<ul>
|
123
|
-
</ul>
|
124
|
-
</li>
|
125
|
-
</ul>
|
126
|
-
</li>
|
127
|
-
</ul>
|
128
|
-
</li>
|
129
|
-
</ul>
|
130
|
-
</li>
|
131
|
-
</ul>
|
132
132
|
</body>
|
133
133
|
</html>
|
134
134
|
end_partial
|
data/test/spec_test.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'mustache'
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'yaml'
|
4
|
+
require 'test/unit'
|
5
|
+
|
6
|
+
# Automatically process !code types into Proc objects
|
7
|
+
YAML::add_builtin_type('code') { |_, val| eval(val['ruby']) }
|
8
|
+
|
9
|
+
# A simple base class for Mustache specs.
|
10
|
+
# Creates a partials directory, then points a (dynamic) subclass of Mustache at
|
11
|
+
# that directory before each test; the partials directory is destroyed after
|
12
|
+
# each test is run.
|
13
|
+
class MustacheSpec < Test::Unit::TestCase
|
14
|
+
def setup
|
15
|
+
@partials = File.join(File.dirname(__FILE__), 'partials')
|
16
|
+
Dir.mkdir(@partials)
|
17
|
+
|
18
|
+
@Mustache = Class.new(Mustache)
|
19
|
+
@Mustache.template_path = @partials
|
20
|
+
end
|
21
|
+
|
22
|
+
def teardown
|
23
|
+
Dir[File.join(@partials, '*')].each { |file| File.delete(file) }
|
24
|
+
Dir.rmdir(@partials)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Extracts the partials from the test, and dumps them into the partials
|
28
|
+
# directory for inclusion.
|
29
|
+
def setup_partials(test)
|
30
|
+
(test['partials'] || {}).each do |name, content|
|
31
|
+
File.open(File.join(@partials, "#{name}.mustache"), 'w') do |f|
|
32
|
+
f.print(content)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Asserts equality between the rendered template and the expected value,
|
38
|
+
# printing additional context data on failure.
|
39
|
+
def assert_mustache_spec(test)
|
40
|
+
actual = @Mustache.render(test['template'], test['data'])
|
41
|
+
|
42
|
+
assert_equal test['expected'], actual, "" <<
|
43
|
+
"#{ test['desc'] }\n" <<
|
44
|
+
"Data: #{ test['data'].inspect }\n" <<
|
45
|
+
"Template: #{ test['template'].inspect }\n" <<
|
46
|
+
"Partials: #{ (test['partials'] || {}).inspect }\n"
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_noop; assert(true); end
|
50
|
+
end
|
51
|
+
|
52
|
+
spec_files = File.join(File.dirname(__FILE__), '..', 'ext', 'spec', 'specs', '*.yml')
|
53
|
+
Dir[spec_files].each do |file|
|
54
|
+
spec = YAML.load_file(file)
|
55
|
+
|
56
|
+
Class.new(MustacheSpec) do
|
57
|
+
define_method :name do
|
58
|
+
File.basename(file).sub(/^./, &:upcase)
|
59
|
+
end
|
60
|
+
|
61
|
+
spec['tests'].each do |test|
|
62
|
+
define_method :"test - #{test['name']}" do
|
63
|
+
setup_partials(test)
|
64
|
+
assert_mustache_spec(test)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
$LOAD_PATH.unshift File.dirname(__FILE__)
|
2
|
+
require 'helper'
|
3
|
+
|
4
|
+
class TemplateTest < Test::Unit::TestCase
|
5
|
+
def test_compile
|
6
|
+
assert_equal %("foo"), Mustache::Template.new("foo").compile
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_compile_with_source
|
10
|
+
assert_equal %("bar"), Mustache::Template.new("foo").compile("bar")
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_token
|
14
|
+
assert_equal [:multi, [:static, "foo"]], Mustache::Template.new("foo").tokens
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_token_with_source
|
18
|
+
assert_equal [:multi, [:static, "bar"]], Mustache::Template.new("foo").tokens("bar")
|
19
|
+
end
|
20
|
+
end
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mustache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 47
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
7
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
8
|
+
- 12
|
9
|
+
- 0
|
10
|
+
version: 0.12.0
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Chris Wanstrath
|
@@ -14,7 +15,7 @@ autorequire:
|
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
17
|
|
17
|
-
date: 2010-
|
18
|
+
date: 2010-12-10 00:00:00 -08:00
|
18
19
|
default_executable:
|
19
20
|
dependencies: []
|
20
21
|
|
@@ -102,6 +103,8 @@ files:
|
|
102
103
|
- test/mustache_test.rb
|
103
104
|
- test/parser_test.rb
|
104
105
|
- test/partial_test.rb
|
106
|
+
- test/spec_test.rb
|
107
|
+
- test/template_test.rb
|
105
108
|
has_rdoc: true
|
106
109
|
homepage: http://github.com/defunkt/mustache
|
107
110
|
licenses: []
|
@@ -112,23 +115,27 @@ rdoc_options: []
|
|
112
115
|
require_paths:
|
113
116
|
- lib
|
114
117
|
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
115
119
|
requirements:
|
116
120
|
- - ">="
|
117
121
|
- !ruby/object:Gem::Version
|
122
|
+
hash: 3
|
118
123
|
segments:
|
119
124
|
- 0
|
120
125
|
version: "0"
|
121
126
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
122
128
|
requirements:
|
123
129
|
- - ">="
|
124
130
|
- !ruby/object:Gem::Version
|
131
|
+
hash: 3
|
125
132
|
segments:
|
126
133
|
- 0
|
127
134
|
version: "0"
|
128
135
|
requirements: []
|
129
136
|
|
130
137
|
rubyforge_project:
|
131
|
-
rubygems_version: 1.3.
|
138
|
+
rubygems_version: 1.3.7
|
132
139
|
signing_key:
|
133
140
|
specification_version: 3
|
134
141
|
summary: Mustache is a framework-agnostic way to render logic-free views.
|