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 +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
|