mustache 0.99.8 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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