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 +0 -14
- data/lib/mustache/context.rb +34 -19
- data/lib/mustache/generator.rb +27 -8
- data/lib/mustache/parser.rb +6 -15
- data/lib/mustache/settings.rb +226 -0
- data/lib/mustache/sinatra.rb +7 -1
- data/lib/mustache/version.rb +1 -1
- data/lib/mustache.rb +118 -212
- data/test/autoloading_test.rb +4 -0
- data/test/fixtures/dot_notation.mustache +5 -1
- data/test/mustache_test.rb +37 -2
- data/test/parser_test.rb +8 -8
- data/test/partial_test.rb +1 -1
- metadata +7 -4
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|
|
data/lib/mustache/context.rb
CHANGED
@@ -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
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
data/lib/mustache/generator.rb
CHANGED
@@ -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 =
|
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 =
|
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 =
|
144
|
-
|
145
|
-
|
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 =
|
154
|
-
|
155
|
-
|
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)
|
data/lib/mustache/parser.rb
CHANGED
@@ -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,
|
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,
|
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,
|
177
|
+
@result << [:mustache, :utag, fetch]
|
176
178
|
else
|
177
|
-
|
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
|
data/lib/mustache/sinatra.rb
CHANGED
@@ -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.
|
data/lib/mustache/version.rb
CHANGED
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
|
-
|
82
|
-
|
83
|
-
render
|
87
|
+
class << self
|
88
|
+
alias_method :to_html, :render
|
89
|
+
alias_method :to_text, :render
|
84
90
|
end
|
85
91
|
|
86
|
-
#
|
87
|
-
|
88
|
-
|
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
|
-
#
|
120
|
-
#
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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
|
-
#
|
196
|
-
#
|
197
|
-
|
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
|
-
|
215
|
+
name = "#{view_namespace}::#{name}"
|
222
216
|
|
223
|
-
if
|
224
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
#
|
246
|
-
#
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
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(/
|
269
|
-
|
270
|
-
|
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
|
-
#
|
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(
|
280
|
-
|
281
|
-
string.
|
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
|
data/test/autoloading_test.rb
CHANGED
@@ -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
|
-
* {{
|
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?}}
|
data/test/mustache_test.rb
CHANGED
@@ -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
|
-
|
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
|
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:
|
4
|
+
hash: 403
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 99
|
9
9
|
- 0
|
10
|
-
version: 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-
|
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
|