mustache 0.99.8 → 1.0.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.
- 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