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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +53 -67
  3. data/Rakefile +4 -12
  4. data/lib/mustache.rb +67 -73
  5. data/lib/mustache/context.rb +57 -48
  6. data/lib/mustache/context_miss.rb +14 -0
  7. data/lib/mustache/generator.rb +5 -1
  8. data/lib/mustache/parser.rb +39 -14
  9. data/lib/mustache/settings.rb +1 -2
  10. data/lib/mustache/template.rb +37 -18
  11. data/lib/mustache/version.rb +1 -1
  12. data/test/autoloading_test.rb +2 -3
  13. data/test/fixtures/comments.rb +0 -1
  14. data/test/fixtures/complex_view.rb +0 -1
  15. data/test/fixtures/crazy_recursive.rb +0 -1
  16. data/test/fixtures/delimiters.rb +0 -1
  17. data/test/fixtures/dot_notation.rb +0 -1
  18. data/test/fixtures/double_section.rb +0 -1
  19. data/test/fixtures/inverted_section.rb +0 -1
  20. data/test/fixtures/lambda.rb +0 -1
  21. data/test/fixtures/liberal.rb +0 -1
  22. data/test/fixtures/method_missing.rb +0 -1
  23. data/test/fixtures/namespaced.rb +0 -1
  24. data/test/fixtures/nested_objects.rb +0 -1
  25. data/test/fixtures/partial_with_module.rb +0 -1
  26. data/test/fixtures/passenger.rb +0 -1
  27. data/test/fixtures/recursive.rb +0 -1
  28. data/test/fixtures/simple.rb +0 -1
  29. data/test/fixtures/template_partial.rb +0 -1
  30. data/test/fixtures/unescaped.rb +0 -1
  31. data/test/helper.rb +3 -2
  32. data/test/mustache_test.rb +13 -13
  33. data/test/parser_test.rb +2 -3
  34. data/test/partial_test.rb +2 -3
  35. data/test/spec_test.rb +2 -2
  36. data/test/template_test.rb +3 -4
  37. metadata +17 -19
  38. data/lib/mustache/sinatra.rb +0 -205
  39. data/lib/rack/bug/panels/mustache_panel.rb +0 -81
  40. data/lib/rack/bug/panels/mustache_panel/mustache_extension.rb +0 -27
  41. data/lib/rack/bug/panels/mustache_panel/view.mustache +0 -46
@@ -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
- # Expect to be passed an instance of `Mustache`.
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. If we're being rendered
38
- # inside a Mustache object as a context, we'll use that one.
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.detect { |frame| frame.is_a?(Mustache) }
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
- # Returns a String.
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
- # Returns the Context.
53
- def push(new)
54
- @stack.unshift(new)
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
- # obj - The object to perform the lookup on.
116
- # key - The key whose value you want.
117
- # default - An optional default value, to return if the
118
- # key is not found.
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 !obj.respond_to?(:to_hash)
123
- # If a class, we need to find tags (methods) per Parser::ALLOWED_CONTENT.
124
- key = key.to_s.tr('-', '_') if key.to_s.include?('-')
125
-
126
- if obj.respond_to?(key)
127
- meth = obj.method(key) rescue proc { obj.send(key) }
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
- class Hash
145
- def to_hash
146
- self
147
- end unless method_defined?(:to_hash)
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
@@ -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].map { |e| compile!(e) }.join
90
+ exp[1..-1].reduce("") { |sum, e| sum << compile!(e) }
87
91
  when :static
88
92
  str(exp[1])
89
93
  when :mustache
@@ -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 &:freeze
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 &:freeze
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 defined using define_method.
280
+ # characters in their method names, they are aliased to
281
+ # better named methods.
282
+ #
283
+
272
284
 
273
- define_method 'scan_tag_#' do |content, fetch, padding, pre_match_position|
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
- define_method 'scan_tag_^' do |content, fetch, padding, pre_match_position|
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
- define_method 'scan_tag_/' do |content, fetch, padding, pre_match_position|
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
- define_method 'scan_tag_!' do |content, fetch, padding, pre_match_position|
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
- define_method 'scan_tag_=' do |content, fetch, padding, pre_match_position|
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
- define_method 'scan_tag_>' do |content, fetch, padding, pre_match_position|
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_<', :'scan_tag_>'
331
+ alias_method :'scan_tag_<', :scan_tag_open_partial
332
+ alias_method :'scan_tag_>', :scan_tag_open_partial
333
+
310
334
 
311
- define_method 'scan_tag_{' do |content, fetch, padding, pre_match_position|
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_&', :'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
@@ -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
  #
@@ -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
- # Simple recursive iterator for tokens
59
- def self.recursor(toks, section, &block)
60
- toks.map do |token|
61
- next unless token.is_a? Array
62
-
63
- if token[0] == :mustache
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
@@ -1,3 +1,3 @@
1
1
  class Mustache
2
- Version = VERSION = '0.99.8'
2
+ VERSION = '1.0.0'
3
3
  end
@@ -1,9 +1,8 @@
1
- $LOAD_PATH.unshift File.dirname(__FILE__)
2
- require 'helper'
1
+ require_relative 'helper'
3
2
 
4
3
  module TestViews; end
5
4
 
6
- class AutoloadingTest < Test::Unit::TestCase
5
+ class AutoloadingTest < Minitest::Test
7
6
  def setup
8
7
  Mustache.view_path = File.dirname(__FILE__) + '/fixtures'
9
8
  end
@@ -1,4 +1,3 @@
1
- $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
1
  require 'mustache'
3
2
 
4
3
  class Comments < Mustache
@@ -1,4 +1,3 @@
1
- $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
1
  require 'mustache'
3
2
 
4
3
  class ComplexView < Mustache