mustache 0.98.0 → 0.99.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/Rakefile CHANGED
@@ -30,14 +30,6 @@ else
30
30
  end
31
31
  end
32
32
 
33
- if command? :kicker
34
- desc "Launch Kicker (like autotest)"
35
- task :kicker do
36
- puts "Kicking... (ctrl+c to cancel)"
37
- exec "kicker -e rake test lib examples"
38
- end
39
- end
40
-
41
33
 
42
34
  #
43
35
  # Ron
@@ -76,12 +68,6 @@ end
76
68
  # Documentation
77
69
  #
78
70
 
79
- begin
80
- require 'sdoc_helpers'
81
- rescue LoadError
82
- warn "sdoc support not enabled. Please gem install sdoc-helpers."
83
- end
84
-
85
71
  desc "Publish to GitHub Pages"
86
72
  task :pages => [ "man:build" ] do
87
73
  Dir['man/*.html'].each do |f|
@@ -12,8 +12,6 @@ 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
-
17
15
  # Expect to be passed an instance of `Mustache`.
18
16
  def initialize(mustache)
19
17
  @stack = [mustache]
@@ -94,34 +92,51 @@ class Mustache
94
92
  # If no second parameter is passed (or raise_on_context_miss is
95
93
  # set to true), will raise a ContextMiss exception on miss.
96
94
  def fetch(name, default = :__raise)
97
- @key = name
98
-
99
95
  @stack.each do |frame|
100
96
  # Prevent infinite recursion.
101
97
  next if frame == self
102
- @frame = frame
103
-
104
- # Is this frame a hash?
105
- hash = frame.respond_to?(:has_key?)
106
-
107
- if hash && frame.has_key?(name)
108
- return frame[name]
109
- elsif hash && frame.has_key?(name.to_s)
110
- @key = name.to_s
111
- return frame[name.to_s]
112
- elsif !hash && frame.respond_to?(name)
113
- @frame = nil
114
- return frame.__send__(name)
98
+
99
+ value = find(frame, name, :__missing)
100
+ if value != :__missing
101
+ return value
115
102
  end
116
103
  end
117
104
 
118
- @frame = @key = nil
119
-
120
105
  if default == :__raise || mustache_in_stack.raise_on_context_miss?
121
106
  raise ContextMiss.new("Can't find #{name} in #{@stack.inspect}")
122
107
  else
123
108
  default
124
109
  end
125
110
  end
111
+
112
+ # Finds a key in an object, using whatever method is most
113
+ # appropriate. If the object is a hash, does a simple hash lookup.
114
+ # If it's an object that responds to the key as a method call,
115
+ # invokes that method. You get the idea.
116
+ #
117
+ # obj - The object to perform the lookup on.
118
+ # key - The key whose value you want.
119
+ # default - An optional default value, to return if the
120
+ # key is not found.
121
+ #
122
+ # Returns the value of key in obj if it is found and default otherwise.
123
+ def find(obj, key, default = nil)
124
+ hash = obj.respond_to?(:has_key?)
125
+
126
+ if hash && obj.has_key?(key)
127
+ obj[key]
128
+ elsif hash && obj.has_key?(key.to_s)
129
+ obj[key.to_s]
130
+ elsif !hash && obj.respond_to?(key)
131
+ meth = obj.method(key)
132
+ if meth.arity == 1
133
+ meth.to_proc
134
+ else
135
+ meth[]
136
+ end
137
+ else
138
+ default
139
+ end
140
+ end
126
141
  end
127
142
  end
@@ -98,7 +98,7 @@ class Mustache
98
98
  # Compile the Ruby for this section now that we know what's
99
99
  # inside the section.
100
100
  ev(<<-compiled)
101
- if v = ctx[#{name.to_sym.inspect}]
101
+ if v = #{compile!(name)}
102
102
  if v == true
103
103
  #{code}
104
104
  elsif v.is_a?(Proc)
@@ -123,7 +123,7 @@ class Mustache
123
123
  # Compile the Ruby for this inverted section now that we know
124
124
  # what's inside.
125
125
  ev(<<-compiled)
126
- v = ctx[#{name.to_sym.inspect}]
126
+ v = #{compile!(name)}
127
127
  if v.nil? || v == false || v.respond_to?(:empty?) && v.empty?
128
128
  #{code}
129
129
  end
@@ -140,9 +140,10 @@ class Mustache
140
140
  # An unescaped tag.
141
141
  def on_utag(name)
142
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)
143
+ v = #{compile!(name)}
144
+ if v.is_a?(Proc)
145
+ v = Mustache::Template.new(v.call.to_s).render(ctx.dup)
146
+ end
146
147
  v.to_s
147
148
  compiled
148
149
  end
@@ -150,13 +151,31 @@ class Mustache
150
151
  # An escaped tag.
151
152
  def on_etag(name)
152
153
  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)
154
+ v = #{compile!(name)}
155
+ if v.is_a?(Proc)
156
+ v = Mustache::Template.new(v.call.to_s).render(ctx.dup)
157
+ end
156
158
  ctx.escapeHTML(v.to_s)
157
159
  compiled
158
160
  end
159
161
 
162
+ def on_fetch(names)
163
+ names = names.map { |n| n.to_sym }
164
+
165
+ if names.length == 0
166
+ "ctx[:to_s]"
167
+ elsif names.length == 1
168
+ "ctx[#{names.first.to_sym.inspect}]"
169
+ else
170
+ initial, *rest = names
171
+ <<-compiled
172
+ #{rest.inspect}.inject(ctx[#{initial.inspect}]) { |value, key|
173
+ value && ctx.find(value, key)
174
+ }
175
+ compiled
176
+ end
177
+ end
178
+
160
179
  # An interpolation-friendly version of a string, for use within a
161
180
  # Ruby string.
162
181
  def ev(s)
@@ -138,17 +138,19 @@ EOF
138
138
  # We found {{ but we can't figure out what's going on inside.
139
139
  error "Illegal content in tag" if content.empty?
140
140
 
141
+ fetch = [:mustache, :fetch, content.split('.')]
142
+
141
143
  # Based on the sigil, do what needs to be done.
142
144
  case type
143
145
  when '#'
144
146
  block = [:multi]
145
- @result << [:mustache, :section, content, block]
147
+ @result << [:mustache, :section, fetch, block]
146
148
  @sections << [content, position, @result]
147
149
  @result = block
148
150
  last_index = 1
149
151
  when '^'
150
152
  block = [:multi]
151
- @result << [:mustache, :inverted_section, content, block]
153
+ @result << [:mustache, :inverted_section, fetch, block]
152
154
  @sections << [content, position, @result]
153
155
  @result = block
154
156
  last_index = 1
@@ -172,20 +174,9 @@ EOF
172
174
  # The closing } in unescaped tags is just a hack for
173
175
  # aesthetics.
174
176
  type = "}" if type == "{"
175
- @result << [:mustache, :utag, content]
177
+ @result << [:mustache, :utag, fetch]
176
178
  else
177
- parts = content.to_s.split(".").reverse
178
-
179
- if parts.size == 0
180
- # implicit iterators - {{.}}
181
- @result << [:mustache, :etag, "to_s"]
182
- else
183
- # dot notation - {{person.name}}
184
- etag = [:mustache, :etag, parts.shift]
185
- @result << parts.inject(etag) { |section, key|
186
- [:mustache, :section, key, section, content]
187
- }
188
- end
179
+ @result << [:mustache, :etag, fetch]
189
180
  end
190
181
 
191
182
  # Skip whitespace and any balancing sigils after the content
@@ -0,0 +1,226 @@
1
+ # Settings which can be configured for all view classes, a single
2
+ # view class, or a single Mustache instance.
3
+ class Mustache
4
+
5
+ #
6
+ # Template Path
7
+ #
8
+
9
+ # The template path informs your Mustache view where to look for its
10
+ # corresponding template. By default it's the current directory (".")
11
+ #
12
+ # A class named Stat with a template_path of "app/templates" will look
13
+ # for "app/templates/stat.mustache"
14
+
15
+ def self.template_path
16
+ @template_path ||= inheritable_config_for :template_path, '.'
17
+ end
18
+
19
+ def self.template_path=(path)
20
+ @template_path = File.expand_path(path)
21
+ @template = nil
22
+ end
23
+
24
+ def template_path
25
+ @template_path ||= self.class.template_path
26
+ end
27
+
28
+ def template_path=(path)
29
+ @template_path = File.expand_path(path)
30
+ @template = nil
31
+ end
32
+
33
+ # Alias for `template_path`
34
+ def self.path
35
+ template_path
36
+ end
37
+ alias_method :path, :template_path
38
+
39
+ # Alias for `template_path`
40
+ def self.path=(path)
41
+ self.template_path = path
42
+ end
43
+ alias_method :path=, :template_path=
44
+
45
+
46
+ #
47
+ # Template Extension
48
+ #
49
+
50
+ # A Mustache template's default extension is 'mustache', but this can be changed.
51
+
52
+ def self.template_extension
53
+ @template_extension ||= inheritable_config_for :template_extension, 'mustache'
54
+ end
55
+
56
+ def self.template_extension=(template_extension)
57
+ @template_extension = template_extension
58
+ @template = nil
59
+ end
60
+
61
+ def template_extension
62
+ @template_extension ||= self.class.template_extension
63
+ end
64
+
65
+ def template_extension=(template_extension)
66
+ @template_extension = template_extension
67
+ @template = nil
68
+ end
69
+
70
+
71
+ #
72
+ # Template Name
73
+ #
74
+
75
+ # The template name is the Mustache template file without any
76
+ # extension or other information. Defaults to `class_name`.
77
+ #
78
+ # You may want to change this if your class is named Stat but you want
79
+ # to re-use another template.
80
+ #
81
+ # class Stat
82
+ # self.template_name = "graphs" # use graphs.mustache
83
+ # end
84
+
85
+ def self.template_name
86
+ @template_name || underscore
87
+ end
88
+
89
+ def self.template_name=(template_name)
90
+ @template_name = template_name
91
+ @template = nil
92
+ end
93
+
94
+ def template_name
95
+ @template_name ||= self.class.template_name
96
+ end
97
+
98
+ def template_name=(template_name)
99
+ @template_name = template_name
100
+ @template = nil
101
+ end
102
+
103
+
104
+ #
105
+ # Template File
106
+ #
107
+
108
+ # The template file is the absolute path of the file Mustache will
109
+ # use as its template. By default it's ./class_name.mustache
110
+
111
+ def self.template_file
112
+ @template_file || "#{path}/#{template_name}.#{template_extension}"
113
+ end
114
+
115
+ def self.template_file=(template_file)
116
+ @template_file = template_file
117
+ @template = nil
118
+ end
119
+
120
+ # The template file is the absolute path of the file Mustache will
121
+ # use as its template. By default it's ./class_name.mustache
122
+ def template_file
123
+ @template_file || "#{path}/#{template_name}.#{template_extension}"
124
+ end
125
+
126
+ def template_file=(template_file)
127
+ @template_file = template_file
128
+ @template = nil
129
+ end
130
+
131
+
132
+ #
133
+ # Template
134
+ #
135
+
136
+ # The template is the actual string Mustache uses as its template.
137
+ # There is a bit of magic here: what we get back is actually a
138
+ # Mustache::Template object, but you can still safely use `template=`
139
+ # with a string.
140
+
141
+ def self.template
142
+ @template ||= templateify(File.read(template_file))
143
+ end
144
+
145
+ def self.template=(template)
146
+ @template = templateify(template)
147
+ end
148
+
149
+ # The template can be set at the instance level.
150
+ def template
151
+ return @template if @template
152
+
153
+ # If they sent any instance-level options use that instead of the class's.
154
+ if @template_path || @template_extension || @template_name || @template_file
155
+ @template = templateify(File.read(template_file))
156
+ else
157
+ @template = self.class.template
158
+ end
159
+ end
160
+
161
+ def template=(template)
162
+ @template = templateify(template)
163
+ end
164
+
165
+
166
+ #
167
+ # Raise on context miss
168
+ #
169
+
170
+ # Should an exception be raised when we cannot find a corresponding method
171
+ # or key in the current context? By default this is false to emulate ctemplate's
172
+ # behavior, but it may be useful to enable when debugging or developing.
173
+ #
174
+ # If set to true and there is a context miss, `Mustache::ContextMiss` will
175
+ # be raised.
176
+
177
+ def self.raise_on_context_miss?
178
+ @raise_on_context_miss
179
+ end
180
+
181
+ def self.raise_on_context_miss=(boolean)
182
+ @raise_on_context_miss = boolean
183
+ end
184
+
185
+ # Instance level version of `Mustache.raise_on_context_miss?`
186
+ def raise_on_context_miss?
187
+ self.class.raise_on_context_miss? || @raise_on_context_miss
188
+ end
189
+
190
+ def raise_on_context_miss=(boolean)
191
+ @raise_on_context_miss = boolean
192
+ end
193
+
194
+
195
+ #
196
+ # View Namespace
197
+ #
198
+
199
+ # The constant under which Mustache will look for views when autoloading.
200
+ # By default the view namespace is `Object`, but it might be nice to set
201
+ # it to something like `Hurl::Views` if your app's main namespace is `Hurl`.
202
+
203
+ def self.view_namespace
204
+ @view_namespace ||= inheritable_config_for(:view_namespace, Object)
205
+ end
206
+
207
+ def self.view_namespace=(namespace)
208
+ @view_namespace = namespace
209
+ end
210
+
211
+
212
+ #
213
+ # View Path
214
+ #
215
+
216
+ # Mustache searches the view path for .rb files to require when asked to find a
217
+ # view class. Defaults to "."
218
+
219
+ def self.view_path
220
+ @view_path ||= inheritable_config_for(:view_path, '.')
221
+ end
222
+
223
+ def self.view_path=(path)
224
+ @view_path = path
225
+ end
226
+ end
@@ -111,7 +111,7 @@ class Mustache
111
111
  end
112
112
 
113
113
  # Returns a View class for a given template name.
114
- def mustache_class(template, options)
114
+ def mustache_class(template, options = {})
115
115
  @template_cache.fetch(:mustache, template) do
116
116
  compile_mustache(template, options)
117
117
  end
@@ -123,6 +123,10 @@ class Mustache
123
123
  options[:templates] ||= settings.views if settings.respond_to?(:views)
124
124
  options[:namespace] ||= self.class
125
125
 
126
+ unless options[:namespace].to_s.include? 'Views'
127
+ options[:namespace] = options[:namespace].const_get(:Views)
128
+ end
129
+
126
130
  factory = Class.new(Mustache) do
127
131
  self.view_namespace = options[:namespace]
128
132
  self.view_path = options[:views]
@@ -137,6 +141,8 @@ class Mustache
137
141
  # Try to find the view class for a given view, e.g.
138
142
  # :view => Hurl::Views::Index.
139
143
  klass = factory.view_class(view)
144
+ klass.view_namespace = options[:namespace]
145
+ klass.view_path = options[:views]
140
146
 
141
147
  # If there is no view class, issue a warning and use the one
142
148
  # we just generated to cache the compiled template.
@@ -1,3 +1,3 @@
1
1
  class Mustache
2
- Version = VERSION = '0.98.0'
2
+ Version = VERSION = '0.99.0'
3
3
  end
data/lib/mustache.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'mustache/template'
2
2
  require 'mustache/context'
3
+ require 'mustache/settings'
3
4
 
4
5
  # Mustache is the base class from which your Mustache subclasses
5
6
  # should inherit (though it can be used on its own).
@@ -70,6 +71,11 @@ require 'mustache/context'
70
71
  # for files containing view classes when using the `view_class` method.
71
72
  #
72
73
  class Mustache
74
+
75
+ #
76
+ # Public API
77
+ #
78
+
73
79
  # Instantiates an instance of this class and calls `render` with
74
80
  # the passed args.
75
81
  #
@@ -78,14 +84,70 @@ class Mustache
78
84
  new.render(*args)
79
85
  end
80
86
 
81
- # Alias for `render`
82
- def self.to_html(*args)
83
- render(*args)
87
+ class << self
88
+ alias_method :to_html, :render
89
+ alias_method :to_text, :render
84
90
  end
85
91
 
86
- # Alias for `render`
87
- def self.to_text(*args)
88
- render(*args)
92
+ # Parses our fancy pants template file and returns normal file with
93
+ # all special {{tags}} and {{#sections}}replaced{{/sections}}.
94
+ #
95
+ # data - A String template or a Hash context. If a Hash is given,
96
+ # we'll try to figure out the template from the class.
97
+ # ctx - A Hash context if `data` is a String template.
98
+ #
99
+ # Examples
100
+ #
101
+ # @view.render("Hi {{thing}}!", :thing => :world)
102
+ #
103
+ # View.template = "Hi {{thing}}!"
104
+ # @view = View.new
105
+ # @view.render(:thing => :world)
106
+ #
107
+ # Returns a rendered String version of a template
108
+ def render(data = template, ctx = {})
109
+ if data.is_a? Hash
110
+ ctx = data
111
+ tpl = templateify(template)
112
+ elsif data.is_a? Symbol
113
+ self.template_name = data
114
+ tpl = templateify(template)
115
+ else
116
+ tpl = templateify(data)
117
+ end
118
+
119
+ return tpl.render(context) if ctx == {}
120
+
121
+ begin
122
+ context.push(ctx)
123
+ tpl.render(context)
124
+ ensure
125
+ context.pop
126
+ end
127
+ end
128
+
129
+ alias_method :to_html, :render
130
+ alias_method :to_text, :render
131
+
132
+ # Context accessors.
133
+ #
134
+ # view = Mustache.new
135
+ # view[:name] = "Jon"
136
+ # view.template = "Hi, {{name}}!"
137
+ # view.render # => "Hi, Jon!"
138
+ def [](key)
139
+ context[key.to_sym]
140
+ end
141
+
142
+ def []=(key, value)
143
+ context[key.to_sym] = value
144
+ end
145
+
146
+ # A helper method which gives access to the context at a given time.
147
+ # Kind of a hack for now, but useful when you're in an iterating section
148
+ # and want access to the hash currently being iterated over.
149
+ def context
150
+ @context ||= Context.new(self)
89
151
  end
90
152
 
91
153
  # Given a file name and an optional context, attempts to load and
@@ -116,91 +178,23 @@ class Mustache
116
178
  self.class.partial(name)
117
179
  end
118
180
 
119
- # The template path informs your Mustache subclass where to look for its
120
- # corresponding template. By default it's the current directory (".")
121
- def self.template_path
122
- @template_path ||= inheritable_config_for :template_path, '.'
123
- end
124
-
125
- def self.template_path=(path)
126
- @template_path = File.expand_path(path)
127
- @template = nil
128
- end
129
-
130
- # Alias for `template_path`
131
- def self.path
132
- template_path
133
- end
134
-
135
- # Alias for `template_path`
136
- def self.path=(path)
137
- self.template_path = path
138
- end
139
-
140
- # A Mustache template's default extension is 'mustache'
141
- def self.template_extension
142
- @template_extension ||= inheritable_config_for :template_extension, 'mustache'
143
- end
144
-
145
- def self.template_extension=(template_extension)
146
- @template_extension = template_extension
147
- @template = nil
148
- end
149
-
150
- # The template name is the Mustache template file without any
151
- # extension or other information. Defaults to `class_name`.
152
- def self.template_name
153
- @template_name || underscore
154
- end
155
-
156
- def self.template_name=(template_name)
157
- @template_name = template_name
158
- @template = nil
159
- end
160
-
161
- # The template file is the absolute path of the file Mustache will
162
- # use as its template. By default it's ./class_name.mustache
163
- def self.template_file
164
- @template_file || "#{path}/#{template_name}.#{template_extension}"
165
- end
166
-
167
- def self.template_file=(template_file)
168
- @template_file = template_file
169
- @template = nil
170
- end
171
-
172
- # The template is the actual string Mustache uses as its template.
173
- # There is a bit of magic here: what we get back is actually a
174
- # Mustache::Template object here, but you can still safely use
175
- # `template=` with a string.
176
- def self.template
177
- @template ||= templateify(File.read(template_file))
178
- end
179
-
180
- def self.template=(template)
181
- @template = templateify(template)
182
- end
183
-
184
- # The constant under which Mustache will look for views. By default it's
185
- # `Object`, but it might be nice to set it to something like `Hurl::Views` if
186
- # your app's main namespace is `Hurl`.
187
- def self.view_namespace
188
- @view_namespace ||= inheritable_config_for(:view_namespace, Object)
181
+ # Override this to provide custom escaping.
182
+ #
183
+ # class PersonView < Mustache
184
+ # def escapeHTML(str)
185
+ # my_html_escape_method(str)
186
+ # end
187
+ # end
188
+ #
189
+ # Returns a String
190
+ def escapeHTML(str)
191
+ CGI.escapeHTML(str)
189
192
  end
190
193
 
191
- def self.view_namespace=(namespace)
192
- @view_namespace = namespace
193
- end
194
194
 
195
- # Mustache searches the view path for .rb files to require when asked to find a
196
- # view class. Defaults to "."
197
- def self.view_path
198
- @view_path ||= inheritable_config_for(:view_path, '.')
199
- end
200
-
201
- def self.view_path=(path)
202
- @view_path = path
203
- end
195
+ #
196
+ # Private API
197
+ #
204
198
 
205
199
  # When given a symbol or string representing a class, will try to produce an
206
200
  # appropriate view class.
@@ -218,38 +212,33 @@ class Mustache
218
212
  end
219
213
 
220
214
  file_name = underscore(name)
221
- namespace = view_namespace
215
+ name = "#{view_namespace}::#{name}"
222
216
 
223
- if namespace.const_defined?(:Views) && namespace::Views.const_defined?(name)
224
- namespace::Views.const_get(name)
225
- elsif namespace.const_defined?(name)
226
- namespace.const_get(name)
217
+ if const = const_get!(name)
218
+ const
227
219
  elsif File.exists?(file = "#{view_path}/#{file_name}.rb")
228
220
  require "#{file}".chomp('.rb')
229
- if namespace.const_defined?(:Views)
230
- namespace::Views.const_get(name)
231
- else
232
- namespace.const_get(name)
233
- end
221
+ const_get!(name) || Mustache
234
222
  else
235
223
  Mustache
236
224
  end
237
- rescue NameError
238
- Mustache
239
225
  end
240
226
 
241
- # Should an exception be raised when we cannot find a corresponding method
242
- # or key in the current context? By default this is false to emulate ctemplate's
243
- # behavior, but it may be useful to enable when debugging or developing.
227
+ # Supercharged version of Module#const_get.
244
228
  #
245
- # If set to true and there is a context miss, `Mustache::ContextMiss` will
246
- # be raised.
247
- def self.raise_on_context_miss?
248
- @raise_on_context_miss
249
- end
250
-
251
- def self.raise_on_context_miss=(boolean)
252
- @raise_on_context_miss = boolean
229
+ # Always searches under Object and can find constants by their full name,
230
+ # e.g. Mustache::Views::Index
231
+ #
232
+ # name - The full constant name to find.
233
+ #
234
+ # Returns the constant if found
235
+ # Returns nil if nothing is found
236
+ def self.const_get!(name)
237
+ name.split('::').inject(Object) do |klass, name|
238
+ klass.const_get(name)
239
+ end
240
+ rescue NameError
241
+ nil
253
242
  end
254
243
 
255
244
  # Has this template already been compiled? Compilation is somewhat
@@ -264,21 +253,28 @@ class Mustache
264
253
  end
265
254
 
266
255
  # template_partial => TemplatePartial
256
+ # template/partial => Template::Partial
267
257
  def self.classify(underscored)
268
- underscored.split(/[-_]/).map do |part|
269
- part[0] = part[0].chr.upcase; part
270
- end.join
258
+ underscored.split('/').map do |namespace|
259
+ namespace.split(/[-_]/).map do |part|
260
+ part[0] = part[0].chr.upcase; part
261
+ end.join
262
+ end.join('::')
271
263
  end
272
264
 
273
- # TemplatePartial => template_partial
265
+ # TemplatePartial => template_partial
266
+ # Template::Partial => template/partial
274
267
  # Takes a string but defaults to using the current class' name.
275
268
  def self.underscore(classified = name)
276
269
  classified = name if classified.to_s.empty?
277
270
  classified = superclass.name if classified.to_s.empty?
278
271
 
279
- string = classified.dup.split('::').last
280
- string[0] = string[0].chr.downcase
281
- string.gsub(/[A-Z]/) { |s| "_#{s.downcase}"}
272
+ string = classified.dup.split("#{view_namespace}::").last
273
+
274
+ string.split('::').map do |part|
275
+ part[0] = part[0].chr.downcase
276
+ part.gsub(/[A-Z]/) { |s| "_#{s.downcase}"}
277
+ end.join('/')
282
278
  end
283
279
 
284
280
  # Turns a string into a Mustache::Template. If passed a Template,
@@ -291,6 +287,10 @@ class Mustache
291
287
  end
292
288
  end
293
289
 
290
+ def templateify(obj)
291
+ self.class.templateify(obj)
292
+ end
293
+
294
294
  # Return the value of the configuration setting on the superclass, or return
295
295
  # the default.
296
296
  #
@@ -301,98 +301,4 @@ class Mustache
301
301
  def self.inheritable_config_for(attr_name, default)
302
302
  superclass.respond_to?(attr_name) ? superclass.send(attr_name) : default
303
303
  end
304
-
305
- def templateify(obj)
306
- self.class.templateify(obj)
307
- end
308
-
309
- # The template can be set at the instance level.
310
- def template
311
- @template ||= self.class.template
312
- end
313
-
314
- def template=(template)
315
- @template = templateify(template)
316
- end
317
-
318
- # Override this to provide custom escaping.
319
- #
320
- # class PersonView < Mustache
321
- # def escapeHTML(str)
322
- # my_html_escape_method(str)
323
- # end
324
- # end
325
- #
326
- # Returns a String
327
- def escapeHTML(str)
328
- CGI.escapeHTML(str)
329
- end
330
-
331
- # Instance level version of `Mustache.raise_on_context_miss?`
332
- def raise_on_context_miss?
333
- self.class.raise_on_context_miss? || @raise_on_context_miss
334
- end
335
- attr_writer :raise_on_context_miss
336
-
337
- # A helper method which gives access to the context at a given time.
338
- # Kind of a hack for now, but useful when you're in an iterating section
339
- # and want access to the hash currently being iterated over.
340
- def context
341
- @context ||= Context.new(self)
342
- end
343
-
344
- # Context accessors.
345
- #
346
- # view = Mustache.new
347
- # view[:name] = "Jon"
348
- # view.template = "Hi, {{name}}!"
349
- # view.render # => "Hi, Jon!"
350
- def [](key)
351
- context[key.to_sym]
352
- end
353
-
354
- def []=(key, value)
355
- context[key.to_sym] = value
356
- end
357
-
358
- # Parses our fancy pants template file and returns normal file with
359
- # all special {{tags}} and {{#sections}}replaced{{/sections}}.
360
- #
361
- # data - A String template or a Hash context. If a Hash is given,
362
- # we'll try to figure out the template from the class.
363
- # ctx - A Hash context if `data` is a String template.
364
- #
365
- # Examples
366
- #
367
- # @view.render("Hi {{thing}}!", :thing => :world)
368
- #
369
- # View.template = "Hi {{thing}}!"
370
- # @view = View.new
371
- # @view.render(:thing => :world)
372
- #
373
- # Returns a rendered String version of a template
374
- def render(data = template, ctx = {})
375
- if data.is_a? Hash
376
- ctx = data
377
- tpl = templateify(template)
378
- elsif data.is_a? Symbol
379
- old_template_name = self.class.template_name
380
- self.class.template_name = data
381
- tpl = templateify(template)
382
- self.class.template_name = old_template_name
383
- else
384
- tpl = templateify(data)
385
- end
386
-
387
- return tpl.render(context) if ctx == {}
388
-
389
- begin
390
- context.push(ctx)
391
- tpl.render(context)
392
- ensure
393
- context.pop
394
- end
395
- end
396
- alias_method :to_html, :render
397
- alias_method :to_text, :render
398
304
  end
@@ -37,6 +37,10 @@ class AutoloadingTest < Test::Unit::TestCase
37
37
  end_render
38
38
  end
39
39
 
40
+ def test_folder_autoload
41
+ assert_equal TestViews::Namespaced, Mustache.view_class('test_views/namespaced')
42
+ end
43
+
40
44
  def test_namespaced_partial_autoload
41
45
  Mustache.view_namespace = TestViews
42
46
  klass = Mustache.view_class(:namespaced_with_partial)
@@ -3,4 +3,8 @@
3
3
  * {{person.hometown.city}}, {{person.hometown.state}}
4
4
  * {{#person}}{{hometown.city}}, {{hometown.state}}{{/person}}
5
5
  * {{#person}}{{#hometown}}{{city}}, {{state}}{{/hometown}}{{/person}}
6
- * {{normal}}
6
+ * {{#person.hometown}}{{city}}, {{state}}{{/person.hometown}}
7
+ * {{normal}}
8
+
9
+ * {{{person.name.first}}} {{&person.name.last}}
10
+ * {{^person.alien?}}{{person.hometown.city}}, {{person.hometown.state}}{{/person.alien?}}
@@ -196,10 +196,13 @@ end_section
196
196
 
197
197
  def test_classify
198
198
  assert_equal 'TemplatePartial', Mustache.classify('template_partial')
199
+ assert_equal 'Admin::TemplatePartial', Mustache.classify('admin/template_partial')
199
200
  end
200
201
 
201
202
  def test_underscore
202
203
  assert_equal 'template_partial', Mustache.underscore('TemplatePartial')
204
+ assert_equal 'admin/template_partial', Mustache.underscore('Admin::TemplatePartial')
205
+ assert_equal 'views/in/sub/directories', Mustache.underscore('Views::In::Sub::Directories')
203
206
  end
204
207
 
205
208
  def test_anon_subclass_underscore
@@ -208,7 +211,12 @@ end_section
208
211
  end
209
212
 
210
213
  def test_namespaced_underscore
211
- assert_equal 'stat_stuff', Mustache.underscore('Views::StatStuff')
214
+ Object.const_set(:Views, Class.new)
215
+ klass = Class.new(Mustache)
216
+ klass.view_namespace = Views
217
+ assert_equal 'stat_stuff', klass.underscore('Views::StatStuff')
218
+
219
+ assert_equal 'views/stat_stuff', Mustache.underscore('Views::StatStuff')
212
220
  end
213
221
 
214
222
  def test_render
@@ -383,6 +391,17 @@ data
383
391
  assert_equal 1, view.calls
384
392
  end
385
393
 
394
+ def test_sections_which_refer_to_unary_method_call_them_as_proc
395
+ kls = Class.new(Mustache) do
396
+ def unary_method(arg)
397
+ "(#{arg})"
398
+ end
399
+ end
400
+
401
+ str = kls.render("{{#unary_method}}test{{/unary_method}}")
402
+ assert_equal "(test)", str
403
+ end
404
+
386
405
  def test_lots_of_staches
387
406
  template = "{{{{foo}}}}"
388
407
 
@@ -520,14 +539,30 @@ template
520
539
  text
521
540
  end
522
541
 
542
+ def test_unescaped_implicit_iterator
543
+ view = Mustache.new
544
+ view.template = "{{#people}}* {{{.}}}\n{{/people}}"
545
+ view[:people] = %w( Chris Mark Scott )
546
+
547
+ assert_equal <<text, view.render
548
+ * Chris
549
+ * Mark
550
+ * Scott
551
+ text
552
+ end
553
+
523
554
  def test_dot_notation
524
- assert_equal <<-text.chomp, DotNotation.render
555
+ assert_equal <<-text, DotNotation.render
525
556
  * Chris Firescythe
526
557
  * 24
527
558
  * Cincinnati, OH
528
559
  * Cincinnati, OH
529
560
  * Cincinnati, OH
561
+ * Cincinnati, OH
530
562
  * Normal
563
+
564
+ * Chris Firescythe
565
+ * Cincinnati, OH
531
566
  text
532
567
  end
533
568
 
data/test/parser_test.rb CHANGED
@@ -22,35 +22,35 @@ EOF
22
22
 
23
23
  expected = [:multi,
24
24
  [:static, "<h1>"],
25
- [:mustache, :etag, "header"],
25
+ [:mustache, :etag, [:mustache, :fetch, ["header"]]],
26
26
  [:static, "</h1>\n"],
27
27
  [:mustache,
28
28
  :section,
29
- "items",
29
+ [:mustache, :fetch, ["items"]],
30
30
  [:multi,
31
31
  [:mustache,
32
32
  :section,
33
- "first",
33
+ [:mustache, :fetch, ["first"]],
34
34
  [:multi,
35
35
  [:static, " <li><strong>"],
36
- [:mustache, :etag, "name"],
36
+ [:mustache, :etag, [:mustache, :fetch, ["name"]]],
37
37
  [:static, "</strong></li>\n"]],
38
38
  %Q' <li><strong>{{name}}</strong></li>\n'],
39
39
  [:mustache,
40
40
  :section,
41
- "link",
41
+ [:mustache, :fetch, ["link"]],
42
42
  [:multi,
43
43
  [:static, " <li><a href=\""],
44
- [:mustache, :etag, "url"],
44
+ [:mustache, :etag, [:mustache, :fetch, ["url"]]],
45
45
  [:static, "\">"],
46
- [:mustache, :etag, "name"],
46
+ [:mustache, :etag, [:mustache, :fetch, ["name"]]],
47
47
  [:static, "</a></li>\n"]],
48
48
  %Q' <li><a href="{{url}}">{{name}}</a></li>\n']],
49
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
50
  [:static, "\n"],
51
51
  [:mustache,
52
52
  :section,
53
- "empty",
53
+ [:mustache, :fetch, ["empty"]],
54
54
  [:multi, [:static, "<p>The list is empty.</p>\n"]],
55
55
  %Q'<p>The list is empty.</p>\n']]
56
56
 
data/test/partial_test.rb CHANGED
@@ -23,8 +23,8 @@ end_partial
23
23
 
24
24
  def test_view_partial_inherits_context
25
25
  klass = Class.new(TemplatePartial)
26
- klass.template_path = File.dirname(__FILE__) + '/fixtures'
27
26
  view = klass.new
27
+ view.template_path = File.dirname(__FILE__) + '/fixtures'
28
28
  view[:titles] = [{:title => :One}, {:title => :Two}]
29
29
  view.template = <<-end_template
30
30
  <h1>Context Test</h1>
metadata CHANGED
@@ -1,21 +1,23 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mustache
3
3
  version: !ruby/object:Gem::Version
4
- hash: 407
4
+ hash: 403
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 98
8
+ - 99
9
9
  - 0
10
- version: 0.98.0
10
+ version: 0.99.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Chris Wanstrath
14
+ - Magnus Holm
15
+ - Pieter van de Bruggen
14
16
  autorequire:
15
17
  bindir: bin
16
18
  cert_chain: []
17
19
 
18
- date: 2011-02-24 00:00:00 -08:00
20
+ date: 2011-02-28 00:00:00 -08:00
19
21
  default_executable:
20
22
  dependencies: []
21
23
 
@@ -46,6 +48,7 @@ files:
46
48
  - lib/mustache/context.rb
47
49
  - lib/mustache/generator.rb
48
50
  - lib/mustache/parser.rb
51
+ - lib/mustache/settings.rb
49
52
  - lib/mustache/sinatra.rb
50
53
  - lib/mustache/template.rb
51
54
  - lib/mustache/version.rb