mustache 0.99.8 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +53 -67
- data/Rakefile +4 -12
- data/lib/mustache.rb +67 -73
- data/lib/mustache/context.rb +57 -48
- data/lib/mustache/context_miss.rb +14 -0
- data/lib/mustache/generator.rb +5 -1
- data/lib/mustache/parser.rb +39 -14
- data/lib/mustache/settings.rb +1 -2
- data/lib/mustache/template.rb +37 -18
- data/lib/mustache/version.rb +1 -1
- data/test/autoloading_test.rb +2 -3
- data/test/fixtures/comments.rb +0 -1
- data/test/fixtures/complex_view.rb +0 -1
- data/test/fixtures/crazy_recursive.rb +0 -1
- data/test/fixtures/delimiters.rb +0 -1
- data/test/fixtures/dot_notation.rb +0 -1
- data/test/fixtures/double_section.rb +0 -1
- data/test/fixtures/inverted_section.rb +0 -1
- data/test/fixtures/lambda.rb +0 -1
- data/test/fixtures/liberal.rb +0 -1
- data/test/fixtures/method_missing.rb +0 -1
- data/test/fixtures/namespaced.rb +0 -1
- data/test/fixtures/nested_objects.rb +0 -1
- data/test/fixtures/partial_with_module.rb +0 -1
- data/test/fixtures/passenger.rb +0 -1
- data/test/fixtures/recursive.rb +0 -1
- data/test/fixtures/simple.rb +0 -1
- data/test/fixtures/template_partial.rb +0 -1
- data/test/fixtures/unescaped.rb +0 -1
- data/test/helper.rb +3 -2
- data/test/mustache_test.rb +13 -13
- data/test/parser_test.rb +2 -3
- data/test/partial_test.rb +2 -3
- data/test/spec_test.rb +2 -2
- data/test/template_test.rb +3 -4
- metadata +17 -19
- data/lib/mustache/sinatra.rb +0 -205
- data/lib/rack/bug/panels/mustache_panel.rb +0 -81
- data/lib/rack/bug/panels/mustache_panel/mustache_extension.rb +0 -27
- data/lib/rack/bug/panels/mustache_panel/view.mustache +0 -46
data/lib/mustache/context.rb
CHANGED
@@ -1,18 +1,16 @@
|
|
1
|
+
require 'mustache/context_miss'
|
2
|
+
|
1
3
|
class Mustache
|
2
|
-
# A ContextMiss is raised whenever a tag's target can not be found
|
3
|
-
# in the current context if `Mustache#raise_on_context_miss?` is
|
4
|
-
# set to true.
|
5
|
-
#
|
6
|
-
# For example, if your View class does not respond to `music` but
|
7
|
-
# your template contains a `{{music}}` tag this exception will be raised.
|
8
|
-
#
|
9
|
-
# By default it is not raised. See Mustache.raise_on_context_miss.
|
10
|
-
class ContextMiss < RuntimeError; end
|
11
4
|
|
12
5
|
# A Context represents the context which a Mustache template is
|
13
6
|
# executed within. All Mustache tags reference keys in the Context.
|
7
|
+
#
|
14
8
|
class Context
|
15
|
-
|
9
|
+
|
10
|
+
# Initializes a Mustache::Context.
|
11
|
+
#
|
12
|
+
# @param [Mustache] mustache A Mustache instance.
|
13
|
+
#
|
16
14
|
def initialize(mustache)
|
17
15
|
@stack = [mustache]
|
18
16
|
end
|
@@ -23,6 +21,7 @@ class Mustache
|
|
23
21
|
# If the Mustache view handling the rendering (e.g. the view
|
24
22
|
# representing your profile page or some other template) responds
|
25
23
|
# to `partial`, we call it and render the result.
|
24
|
+
#
|
26
25
|
def partial(name, indentation = '')
|
27
26
|
# Look for the first Mustache in the stack.
|
28
27
|
mustache = mustache_in_stack
|
@@ -34,32 +33,43 @@ class Mustache
|
|
34
33
|
mustache.render(part, self)
|
35
34
|
end
|
36
35
|
|
37
|
-
# Find the first Mustache in the stack.
|
38
|
-
#
|
36
|
+
# Find the first Mustache in the stack.
|
37
|
+
#
|
38
|
+
# If we're being rendered inside a Mustache object as a context,
|
39
|
+
# we'll use that one.
|
40
|
+
#
|
41
|
+
# @return [Mustache] First Mustache in the stack.
|
42
|
+
#
|
39
43
|
def mustache_in_stack
|
40
|
-
@stack.
|
44
|
+
@stack.find { |frame| frame.is_a?(Mustache) }
|
41
45
|
end
|
42
46
|
|
43
47
|
# Allows customization of how Mustache escapes things.
|
44
48
|
#
|
45
|
-
#
|
49
|
+
# @param [String] str String to escape.
|
50
|
+
#
|
51
|
+
# @return [String] Escaped HTML string.
|
52
|
+
#
|
46
53
|
def escapeHTML(str)
|
47
54
|
mustache_in_stack.escapeHTML(str)
|
48
55
|
end
|
49
56
|
|
50
57
|
# Adds a new object to the context's internal stack.
|
51
58
|
#
|
52
|
-
#
|
53
|
-
|
54
|
-
|
59
|
+
# @param [Object] new_obj Object to be added to the internal stack.
|
60
|
+
#
|
61
|
+
# @return [Context] Returns the Context.
|
62
|
+
#
|
63
|
+
def push(new_obj)
|
64
|
+
@stack.unshift(new_obj)
|
55
65
|
self
|
56
66
|
end
|
57
|
-
alias_method :update, :push
|
58
67
|
|
59
68
|
# Removes the most recently added object from the context's
|
60
69
|
# internal stack.
|
61
70
|
#
|
62
|
-
# Returns the Context.
|
71
|
+
# @return [Context] Returns the Context.
|
72
|
+
#
|
63
73
|
def pop
|
64
74
|
@stack.shift
|
65
75
|
self
|
@@ -112,37 +122,36 @@ class Mustache
|
|
112
122
|
# If it's an object that responds to the key as a method call,
|
113
123
|
# invokes that method. You get the idea.
|
114
124
|
#
|
115
|
-
#
|
116
|
-
#
|
117
|
-
# default
|
118
|
-
#
|
125
|
+
# @param [Object] obj The object to perform the lookup on.
|
126
|
+
# @param [String,Symbol] key The key whose value you want
|
127
|
+
# @param [Object] default An optional default value, to return if the key is not found.
|
128
|
+
#
|
129
|
+
# @return [Object] The value of key in object if it is found, and default otherwise.
|
119
130
|
#
|
120
|
-
# Returns the value of key in obj if it is found and default otherwise.
|
121
131
|
def find(obj, key, default = nil)
|
122
|
-
if
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
meth.arity == 1 ? meth.to_proc : meth[]
|
130
|
-
else
|
131
|
-
default
|
132
|
-
end
|
133
|
-
elsif obj.has_key?(key)
|
134
|
-
obj[key]
|
135
|
-
elsif obj.has_key?(key.to_s)
|
136
|
-
obj[key.to_s]
|
137
|
-
else
|
138
|
-
obj[key] || default
|
139
|
-
end
|
132
|
+
return find_in_hash(obj, key, default) if obj.respond_to?(:to_hash)
|
133
|
+
|
134
|
+
key = to_tag(key)
|
135
|
+
return default unless obj.respond_to?(key)
|
136
|
+
|
137
|
+
meth = obj.method(key) rescue proc { obj.send(key) }
|
138
|
+
meth.arity == 1 ? meth.to_proc : meth.call
|
140
139
|
end
|
141
|
-
end
|
142
|
-
end
|
143
140
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
|
145
|
+
# If a class, we need to find tags (methods) per Parser::ALLOWED_CONTENT.
|
146
|
+
def to_tag key
|
147
|
+
key.to_s.include?('-') ? key.to_s.tr('-', '_') : key
|
148
|
+
end
|
149
|
+
|
150
|
+
def find_in_hash obj, key, default
|
151
|
+
return obj[key] if obj.has_key?(key)
|
152
|
+
return obj[key.to_s] if obj.has_key?(key.to_s)
|
153
|
+
|
154
|
+
obj.fetch(key, default)
|
155
|
+
end
|
156
|
+
end
|
148
157
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Mustache
|
2
|
+
|
3
|
+
# A ContextMiss is raised whenever a tag's target can not be found
|
4
|
+
# in the current context if `Mustache#raise_on_context_miss?` is
|
5
|
+
# set to true.
|
6
|
+
#
|
7
|
+
# For example, if your View class does not respond to `music` but
|
8
|
+
# your template contains a `{{music}}` tag this exception will be raised.
|
9
|
+
#
|
10
|
+
# By default it is not raised. See Mustache.raise_on_context_miss.
|
11
|
+
#
|
12
|
+
class ContextMiss < RuntimeError; end
|
13
|
+
|
14
|
+
end
|
data/lib/mustache/generator.rb
CHANGED
@@ -37,6 +37,10 @@ class Mustache
|
|
37
37
|
"\"#{compile!(exp)}\""
|
38
38
|
end
|
39
39
|
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
|
40
44
|
# Given an array of tokens, converts them into Ruby code. In
|
41
45
|
# particular there are three types of expressions we are concerned
|
42
46
|
# with:
|
@@ -83,7 +87,7 @@ class Mustache
|
|
83
87
|
def compile!(exp)
|
84
88
|
case exp.first
|
85
89
|
when :multi
|
86
|
-
exp[1..-1].
|
90
|
+
exp[1..-1].reduce("") { |sum, e| sum << compile!(e) }
|
87
91
|
when :static
|
88
92
|
str(exp[1])
|
89
93
|
when :mustache
|
data/lib/mustache/parser.rb
CHANGED
@@ -75,14 +75,14 @@ EOF
|
|
75
75
|
# After these types of tags, all whitespace until the end of the line will
|
76
76
|
# be skipped if they are the first (and only) non-whitespace content on
|
77
77
|
# the line.
|
78
|
-
SKIP_WHITESPACE = [ '#', '^', '/', '<', '>', '=', '!' ].map
|
78
|
+
SKIP_WHITESPACE = [ '#', '^', '/', '<', '>', '=', '!' ].map(&:freeze)
|
79
79
|
|
80
80
|
# The content allowed in a tag name.
|
81
81
|
ALLOWED_CONTENT = /(\w|[?!\/.-])*/
|
82
82
|
|
83
83
|
# These types of tags allow any content,
|
84
84
|
# the rest only allow ALLOWED_CONTENT.
|
85
|
-
ANY_CONTENT = [ '!', '=' ].map
|
85
|
+
ANY_CONTENT = [ '!', '=' ].map(&:freeze)
|
86
86
|
|
87
87
|
attr_reader :scanner, :result
|
88
88
|
attr_writer :otag, :ctag
|
@@ -105,11 +105,11 @@ EOF
|
|
105
105
|
|
106
106
|
# Given a string template, returns an array of tokens.
|
107
107
|
def compile(template)
|
108
|
+
@encoding = nil
|
109
|
+
|
108
110
|
if template.respond_to?(:encoding)
|
109
111
|
@encoding = template.encoding
|
110
112
|
template = template.dup.force_encoding("BINARY")
|
111
|
-
else
|
112
|
-
@encoding = nil
|
113
113
|
end
|
114
114
|
|
115
115
|
# Keeps information about opened sections.
|
@@ -131,6 +131,10 @@ EOF
|
|
131
131
|
@result
|
132
132
|
end
|
133
133
|
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
|
134
138
|
# Find {{mustaches}} and add them to the @result array.
|
135
139
|
def scan_tags
|
136
140
|
# Scan until we hit an opening delimiter.
|
@@ -178,6 +182,7 @@ EOF
|
|
178
182
|
else
|
179
183
|
@result << [:mustache, :etag, fetch, offset]
|
180
184
|
end
|
185
|
+
|
181
186
|
# The closing } in unescaped tags is just a hack for
|
182
187
|
# aesthetics.
|
183
188
|
type = "}" if type == "{"
|
@@ -267,24 +272,35 @@ EOF
|
|
267
272
|
raise SyntaxError.new(message, pos)
|
268
273
|
end
|
269
274
|
|
275
|
+
|
276
|
+
#
|
277
|
+
# Scan tags
|
278
|
+
#
|
270
279
|
# These methods are called in `scan_tags`. Because they contain nonstandard
|
271
|
-
# characters in their method names, they are
|
280
|
+
# characters in their method names, they are aliased to
|
281
|
+
# better named methods.
|
282
|
+
#
|
283
|
+
|
272
284
|
|
273
|
-
|
285
|
+
def scan_tag_block content, fetch, padding, pre_match_position
|
274
286
|
block = [:multi]
|
275
287
|
@result << [:mustache, :section, fetch, offset, block]
|
276
288
|
@sections << [content, position, @result]
|
277
289
|
@result = block
|
278
290
|
end
|
291
|
+
alias_method :'scan_tag_#', :scan_tag_block
|
279
292
|
|
280
|
-
|
293
|
+
|
294
|
+
def scan_tag_inverted content, fetch, padding, pre_match_position
|
281
295
|
block = [:multi]
|
282
296
|
@result << [:mustache, :inverted_section, fetch, offset, block]
|
283
297
|
@sections << [content, position, @result]
|
284
298
|
@result = block
|
285
299
|
end
|
300
|
+
alias_method :'scan_tag_^', :scan_tag_inverted
|
301
|
+
|
286
302
|
|
287
|
-
|
303
|
+
def scan_tag_close content, fetch, padding, pre_match_position
|
288
304
|
section, pos, result = @sections.pop
|
289
305
|
raw = @scanner.pre_match[pos[3]...pre_match_position] + padding
|
290
306
|
(@result = result).last << raw << [self.otag, self.ctag]
|
@@ -295,23 +311,32 @@ EOF
|
|
295
311
|
error "Unclosed section #{section.inspect}", pos
|
296
312
|
end
|
297
313
|
end
|
314
|
+
alias_method :'scan_tag_/', :scan_tag_close
|
298
315
|
|
299
|
-
|
316
|
+
|
317
|
+
def scan_tag_comment content, fetch, padding, pre_match_position
|
300
318
|
end
|
319
|
+
alias_method :'scan_tag_!', :scan_tag_comment
|
320
|
+
|
301
321
|
|
302
|
-
|
322
|
+
def scan_tag_delimiter content, fetch, padding, pre_match_position
|
303
323
|
self.otag, self.ctag = content.split(' ', 2)
|
304
324
|
end
|
325
|
+
alias_method :'scan_tag_=', :scan_tag_delimiter
|
305
326
|
|
306
|
-
|
327
|
+
|
328
|
+
def scan_tag_open_partial content, fetch, padding, pre_match_position
|
307
329
|
@result << [:mustache, :partial, content, offset, padding]
|
308
330
|
end
|
309
|
-
alias_method :'scan_tag_<', :
|
331
|
+
alias_method :'scan_tag_<', :scan_tag_open_partial
|
332
|
+
alias_method :'scan_tag_>', :scan_tag_open_partial
|
333
|
+
|
310
334
|
|
311
|
-
|
335
|
+
def scan_tag_unescaped content, fetch, padding, pre_match_position
|
312
336
|
@result << [:mustache, :utag, fetch, offset]
|
313
337
|
end
|
314
|
-
alias_method :'scan_tag_
|
338
|
+
alias_method :'scan_tag_{', :'scan_tag_unescaped'
|
339
|
+
alias_method :'scan_tag_&', :'scan_tag_unescaped'
|
315
340
|
|
316
341
|
end
|
317
342
|
end
|
data/lib/mustache/settings.rb
CHANGED
@@ -24,6 +24,7 @@ class Mustache
|
|
24
24
|
def template_path
|
25
25
|
@template_path ||= self.class.template_path
|
26
26
|
end
|
27
|
+
alias_method :path, :template_path
|
27
28
|
|
28
29
|
def template_path=(path)
|
29
30
|
@template_path = File.expand_path(path)
|
@@ -34,13 +35,11 @@ class Mustache
|
|
34
35
|
def self.path
|
35
36
|
template_path
|
36
37
|
end
|
37
|
-
alias_method :path, :template_path
|
38
38
|
|
39
39
|
# Alias for `template_path`
|
40
40
|
def self.path=(path)
|
41
41
|
self.template_path = path
|
42
42
|
end
|
43
|
-
alias_method :path=, :template_path=
|
44
43
|
|
45
44
|
|
46
45
|
#
|
data/lib/mustache/template.rb
CHANGED
@@ -51,26 +51,19 @@ class Mustache
|
|
51
51
|
alias_method :to_s, :compile
|
52
52
|
|
53
53
|
# Returns an array of tokens for a given template.
|
54
|
+
#
|
55
|
+
# @return [Array] Array of tokens.
|
56
|
+
#
|
54
57
|
def tokens(src = @source)
|
55
58
|
Parser.new.compile(src)
|
56
59
|
end
|
57
60
|
|
58
|
-
#
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
new_token, new_section, result, stop = yield(token, section)
|
65
|
-
[ result ] + ( stop ? [] : recursor(new_token, new_section, &block))
|
66
|
-
else
|
67
|
-
recursor(token, section, &block)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
# Returns an array of tags
|
73
|
-
# Tags that belong to sections will be of the form `section1.tag`
|
61
|
+
# Returns an array of tags.
|
62
|
+
#
|
63
|
+
# Tags that belong to sections will be of the form `section1.tag`.
|
64
|
+
#
|
65
|
+
# @return [Array] Returns an array of tags.
|
66
|
+
#
|
74
67
|
def tags
|
75
68
|
Template.recursor(tokens, []) do |token, section|
|
76
69
|
if [:etag, :utag].include?(token[1])
|
@@ -83,8 +76,12 @@ class Mustache
|
|
83
76
|
end.flatten.reject(&:nil?).uniq
|
84
77
|
end
|
85
78
|
|
86
|
-
# Returns an array of sections
|
79
|
+
# Returns an array of sections.
|
80
|
+
#
|
87
81
|
# Sections that belong to other sections will be of the form `section1.childsection`
|
82
|
+
#
|
83
|
+
# @return [Array] Returns an array of section.
|
84
|
+
#
|
88
85
|
def sections
|
89
86
|
Template.recursor(tokens, []) do |token, section|
|
90
87
|
if [:section, :inverted_section].include?(token[1])
|
@@ -96,8 +93,12 @@ class Mustache
|
|
96
93
|
end.flatten.reject(&:nil?).uniq
|
97
94
|
end
|
98
95
|
|
99
|
-
# Returns an array of partials
|
96
|
+
# Returns an array of partials.
|
97
|
+
#
|
100
98
|
# Partials that belong to sections are included, but the section name is not preserved
|
99
|
+
#
|
100
|
+
# @return [Array] Returns an array of partials.
|
101
|
+
#
|
101
102
|
def partials
|
102
103
|
Template.recursor(tokens, []) do |token, section|
|
103
104
|
if token[1] == :partial
|
@@ -107,5 +108,23 @@ class Mustache
|
|
107
108
|
end
|
108
109
|
end.flatten.reject(&:nil?).uniq
|
109
110
|
end
|
111
|
+
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
|
116
|
+
# Simple recursive iterator for tokens
|
117
|
+
def self.recursor(toks, section, &block)
|
118
|
+
toks.map do |token|
|
119
|
+
next unless token.is_a? Array
|
120
|
+
|
121
|
+
if token.first == :mustache
|
122
|
+
new_token, new_section, result, stop = yield(token, section)
|
123
|
+
[ result ] + ( stop ? [] : recursor(new_token, new_section, &block))
|
124
|
+
else
|
125
|
+
recursor(token, section, &block)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
110
129
|
end
|
111
130
|
end
|
data/lib/mustache/version.rb
CHANGED
data/test/autoloading_test.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
-
|
2
|
-
require 'helper'
|
1
|
+
require_relative 'helper'
|
3
2
|
|
4
3
|
module TestViews; end
|
5
4
|
|
6
|
-
class AutoloadingTest < Test
|
5
|
+
class AutoloadingTest < Minitest::Test
|
7
6
|
def setup
|
8
7
|
Mustache.view_path = File.dirname(__FILE__) + '/fixtures'
|
9
8
|
end
|
data/test/fixtures/comments.rb
CHANGED