mustache 0.98.0 → 0.99.0

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