mustache 0.1.4 → 0.2.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/.kick +26 -0
- data/README.md +12 -8
- data/Rakefile +13 -3
- data/benchmarks/speed.rb +7 -5
- data/examples/inner_partial.txt +1 -0
- data/examples/template_partial.rb +4 -0
- data/examples/template_partial.txt +4 -0
- data/lib/mustache.rb +146 -158
- data/lib/mustache/context.rb +20 -0
- data/lib/mustache/template.rb +127 -0
- data/lib/mustache/version.rb +1 -1
- data/test/mustache_test.rb +25 -13
- metadata +7 -2
data/.kick
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# take control of the growl notifications
|
2
|
+
module GrowlHacks
|
3
|
+
def growl(type, subject, body, *args, &block)
|
4
|
+
case type
|
5
|
+
when Kicker::GROWL_NOTIFICATIONS[:succeeded]
|
6
|
+
puts subject = "Success"
|
7
|
+
body = body.split("\n").last
|
8
|
+
when Kicker::GROWL_NOTIFICATIONS[:failed]
|
9
|
+
subject = "Failure"
|
10
|
+
puts body
|
11
|
+
body = body.split("\n").last
|
12
|
+
else
|
13
|
+
return nil
|
14
|
+
end
|
15
|
+
super(type, subject, body, *args, &block)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Kicker.send :extend, GrowlHacks
|
20
|
+
|
21
|
+
# no logging
|
22
|
+
Kicker::Utils.module_eval do
|
23
|
+
def log(message)
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
data/README.md
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
Mustache
|
2
2
|
=========
|
3
3
|
|
4
|
-
Inspired by [ctemplate]
|
5
|
-
|
6
|
-
[et](http://www.ivan.fomichev.name/2008/05/erlang-template-engine-prototype.html),
|
7
|
-
Mustache is a framework-agnostic way to render logic-free views.
|
4
|
+
Inspired by [ctemplate][1] and [et][2], Mustache is a
|
5
|
+
framework-agnostic way to render logic-free views.
|
8
6
|
|
9
7
|
As ctemplates says, "It emphasizes separating logic from presentation:
|
10
8
|
it is impossible to embed application logic in this template language."
|
@@ -82,7 +80,7 @@ Now let's write the template:
|
|
82
80
|
This template references our view methods. To bring it all together,
|
83
81
|
here's the code to render actual HTML;
|
84
82
|
|
85
|
-
Simple.
|
83
|
+
Simple.render
|
86
84
|
|
87
85
|
Which returns the following:
|
88
86
|
|
@@ -184,7 +182,7 @@ We can fill in the values at will:
|
|
184
182
|
dict = Dict.new
|
185
183
|
dict[:name] = 'George'
|
186
184
|
dict[:value] = 100
|
187
|
-
dict.
|
185
|
+
dict.render
|
188
186
|
|
189
187
|
Which returns:
|
190
188
|
|
@@ -194,7 +192,7 @@ Which returns:
|
|
194
192
|
We can re-use the same object, too:
|
195
193
|
|
196
194
|
dict[:name] = 'Tony'
|
197
|
-
dict.
|
195
|
+
dict.render
|
198
196
|
Hello Tony
|
199
197
|
You have just won $100!
|
200
198
|
|
@@ -299,7 +297,7 @@ normal class.
|
|
299
297
|
|
300
298
|
Now:
|
301
299
|
|
302
|
-
Simple.new(request.ssl?).
|
300
|
+
Simple.new(request.ssl?).render
|
303
301
|
|
304
302
|
Convoluted but you get the idea.
|
305
303
|
|
@@ -312,6 +310,9 @@ Mustache ships with Sinatra integration. Please see
|
|
312
310
|
<http://defunkt.github.com/mustache/classes/Mustache/Sinatra.html> for
|
313
311
|
complete documentation.
|
314
312
|
|
313
|
+
An example Sinatra application is also provided:
|
314
|
+
<http://github.com/defunkt/mustache-sinatra-example>
|
315
|
+
|
315
316
|
|
316
317
|
Installation
|
317
318
|
------------
|
@@ -343,3 +344,6 @@ Meta
|
|
343
344
|
* Test: <http://runcoderun.com/defunkt/mustache>
|
344
345
|
* Gems: <http://gemcutter.org/gems/mustache>
|
345
346
|
* Boss: Chris Wanstrath :: <http://github.com/defunkt>
|
347
|
+
|
348
|
+
[1]: http://code.google.com/p/google-ctemplate/
|
349
|
+
[2]: http://www.ivan.fomichev.name/2008/05/erlang-template-engine-prototype.html
|
data/Rakefile
CHANGED
@@ -9,6 +9,12 @@ Rake::TestTask.new do |t|
|
|
9
9
|
t.verbose = false
|
10
10
|
end
|
11
11
|
|
12
|
+
desc "Launch Kicker (like autotest)"
|
13
|
+
task :kicker do
|
14
|
+
puts "Kicking... (ctrl+c to cancel)"
|
15
|
+
exec "kicker -e rake test lib"
|
16
|
+
end
|
17
|
+
|
12
18
|
begin
|
13
19
|
require 'jeweler'
|
14
20
|
$LOAD_PATH.unshift 'lib'
|
@@ -23,7 +29,8 @@ begin
|
|
23
29
|
gemspec.version = Mustache::Version
|
24
30
|
end
|
25
31
|
rescue LoadError
|
26
|
-
puts "Jeweler not available.
|
32
|
+
puts "Jeweler not available."
|
33
|
+
puts "Install it with: gem install jeweler"
|
27
34
|
end
|
28
35
|
|
29
36
|
begin
|
@@ -34,6 +41,9 @@ end
|
|
34
41
|
|
35
42
|
desc "Push a new version to Gemcutter"
|
36
43
|
task :publish => [ :gemspec, :build ] do
|
37
|
-
system "
|
38
|
-
|
44
|
+
system "git tag v#{Mustache::Version}"
|
45
|
+
system "git push origin v#{Mustache::Version}"
|
46
|
+
system "gem push pkg/mustache-#{Mustache::Version}.gem"
|
47
|
+
system "git clean -fd"
|
48
|
+
exec "rake pages"
|
39
49
|
end
|
data/benchmarks/speed.rb
CHANGED
@@ -41,9 +41,11 @@ end
|
|
41
41
|
|
42
42
|
content = File.read(ComplexView.template_file)
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
44
|
+
unless ENV['CACHED']
|
45
|
+
bench '{ w/o caching' do
|
46
|
+
tpl = ComplexView.new
|
47
|
+
tpl.template = content
|
48
|
+
tpl[:item] = items
|
49
|
+
tpl.to_html
|
50
|
+
end
|
49
51
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
## Again, {{title}}! ##
|
data/lib/mustache.rb
CHANGED
@@ -1,172 +1,126 @@
|
|
1
|
-
require '
|
2
|
-
|
1
|
+
require 'mustache/template'
|
2
|
+
require 'mustache/context'
|
3
|
+
|
4
|
+
# Mustache is the base class from which your Mustache subclasses
|
5
|
+
# should inherit (though it can be used on its own).
|
6
|
+
#
|
7
|
+
# The typical Mustache workflow is as follows:
|
8
|
+
#
|
9
|
+
# * Create a Mustache subclass: class Stats < Mustache
|
10
|
+
# * Create a template: stats.html
|
11
|
+
# * Instantiate an instance: view = Stats.new
|
12
|
+
# * Render that instance: view.render
|
13
|
+
#
|
14
|
+
# You can skip the instantiation by calling `Stats.render` directly.
|
15
|
+
#
|
16
|
+
# While Mustache will do its best to load and render a template for
|
17
|
+
# you, this process is completely customizable using a few options.
|
18
|
+
#
|
19
|
+
# All settings can be overriden at either the class or instance
|
20
|
+
# level. For example, going with the above example, we can do either
|
21
|
+
# `Stats.template_path = "/usr/local/templates"` or
|
22
|
+
# `view.template_path = "/usr/local/templates"`
|
23
|
+
#
|
24
|
+
# Here are the available options:
|
25
|
+
#
|
26
|
+
# * template_path
|
27
|
+
#
|
28
|
+
# The `template_path` setting determines the path Mustache uses when
|
29
|
+
# looking for a template. By default it is "."
|
30
|
+
# Setting it to /usr/local/templates, for example, means (given all
|
31
|
+
# other settings are default) a Mustache subclass `Stats` will try to
|
32
|
+
# load /usr/local/templates/stats.html
|
33
|
+
#
|
34
|
+
# * template_extension
|
35
|
+
#
|
36
|
+
# The `template_extension` is the extension Mustache uses when looking
|
37
|
+
# for template files. By default it is "html"
|
38
|
+
#
|
39
|
+
# * template_file
|
40
|
+
#
|
41
|
+
# You can tell Mustache exactly which template to us with this
|
42
|
+
# setting. It can be a relative or absolute path.
|
43
|
+
#
|
44
|
+
# * template
|
45
|
+
#
|
46
|
+
# Sometimes you want Mustache to render a string, not a file. In those
|
47
|
+
# cases you may set the `template` setting. For example:
|
48
|
+
#
|
49
|
+
# >> Mustache.render("Hello {{planet}}", :planet => "World!")
|
50
|
+
# => "Hello World!"
|
51
|
+
#
|
3
52
|
class Mustache
|
4
|
-
#
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
@template_path = template_path
|
9
|
-
@tmpid = 0
|
10
|
-
end
|
11
|
-
|
12
|
-
def render(context)
|
13
|
-
class << self; self; end.class_eval <<-EOF, __FILE__, __LINE__ - 1
|
14
|
-
def render(ctx)
|
15
|
-
#{compile}
|
16
|
-
end
|
17
|
-
EOF
|
18
|
-
render(context)
|
19
|
-
end
|
20
|
-
|
21
|
-
def compile(src = @source)
|
22
|
-
"\"#{compile_sections(src)}\""
|
23
|
-
end
|
24
|
-
|
25
|
-
## private
|
26
|
-
|
27
|
-
# {{#sections}}okay{{/sections}}
|
28
|
-
#
|
29
|
-
# Sections can return true, false, or an enumerable.
|
30
|
-
# If true, the section is displayed.
|
31
|
-
# If false, the section is not displayed.
|
32
|
-
# If enumerable, the return value is iterated over (a for loop).
|
33
|
-
def compile_sections(src)
|
34
|
-
res = ""
|
35
|
-
while src =~ /^\s*\{\{\#(.+)\}\}\n*(.+)^\s*\{\{\/\1\}\}\n*/m
|
36
|
-
res << compile_tags($`)
|
37
|
-
name = $1.strip.to_sym.inspect
|
38
|
-
code = compile($2)
|
39
|
-
ctxtmp = "ctx#{tmpid}"
|
40
|
-
res << ev("(v = ctx[#{name}]) ? v.respond_to?(:each) ? "\
|
41
|
-
"(#{ctxtmp}=ctx.dup; r=v.map{|h|ctx.merge!(h);#{code}}.join; "\
|
42
|
-
"ctx.replace(#{ctxtmp});r) : #{code} : ''")
|
43
|
-
src = $'
|
44
|
-
end
|
45
|
-
res << compile_tags(src)
|
46
|
-
end
|
47
|
-
|
48
|
-
# Find and replace all non-section tags.
|
49
|
-
# In particular we look for four types of tags:
|
50
|
-
# 1. Escaped variable tags - {{var}}
|
51
|
-
# 2. Unescaped variable tags - {{{var}}}
|
52
|
-
# 3. Comment variable tags - {{! comment}
|
53
|
-
# 4. Partial tags - {{< partial_name }}
|
54
|
-
def compile_tags(src)
|
55
|
-
res = ""
|
56
|
-
while src =~ /\{\{(!|<|\{)?([^\/#]+?)\1?\}\}+/
|
57
|
-
res << str($`)
|
58
|
-
case $1
|
59
|
-
when '!'
|
60
|
-
# ignore comments
|
61
|
-
when '<'
|
62
|
-
res << compile_partial($2.strip)
|
63
|
-
when '{'
|
64
|
-
res << utag($2.strip)
|
65
|
-
else
|
66
|
-
res << etag($2.strip)
|
67
|
-
end
|
68
|
-
src = $'
|
69
|
-
end
|
70
|
-
res << str(src)
|
71
|
-
end
|
72
|
-
|
73
|
-
# Partials are basically a way to render views from inside other views.
|
74
|
-
def compile_partial(name)
|
75
|
-
klass = Mustache.classify(name)
|
76
|
-
if Object.const_defined?(klass)
|
77
|
-
ev("#{klass}.render")
|
78
|
-
else
|
79
|
-
src = File.read(@template_path + '/' + name + '.html')
|
80
|
-
compile(src)[1..-2]
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
# Generate a temporary id.
|
85
|
-
def tmpid
|
86
|
-
@tmpid += 1
|
87
|
-
end
|
88
|
-
|
89
|
-
def str(s)
|
90
|
-
s.inspect[1..-2]
|
91
|
-
end
|
92
|
-
|
93
|
-
def etag(s)
|
94
|
-
ev("Mustache.escape(ctx[#{s.strip.to_sym.inspect}])")
|
95
|
-
end
|
96
|
-
|
97
|
-
def utag(s)
|
98
|
-
ev("ctx[#{s.strip.to_sym.inspect}]")
|
99
|
-
end
|
53
|
+
# Helper method for quickly instantiating and rendering a view.
|
54
|
+
def self.render(*args)
|
55
|
+
new.render(*args)
|
56
|
+
end
|
100
57
|
|
101
|
-
|
102
|
-
|
103
|
-
|
58
|
+
# Alias for `render`
|
59
|
+
def self.to_html(*args)
|
60
|
+
render(*args)
|
104
61
|
end
|
105
62
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
end
|
63
|
+
# Alias for `render`
|
64
|
+
def self.to_text(*args)
|
65
|
+
render(*args)
|
66
|
+
end
|
111
67
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
@mustache.send(name)
|
117
|
-
else
|
118
|
-
raise "Can't find #{name} in #{inspect}"
|
119
|
-
end
|
120
|
-
end
|
68
|
+
# The template path informs your Mustache subclass where to look for its
|
69
|
+
# corresponding template. By default it's the current directory (".")
|
70
|
+
def self.template_path
|
71
|
+
@template_path || '.'
|
121
72
|
end
|
122
73
|
|
123
|
-
|
124
|
-
|
125
|
-
new.render(*args)
|
74
|
+
def self.template_path=(path)
|
75
|
+
@template_path = File.expand_path(path)
|
126
76
|
end
|
127
77
|
|
128
|
-
|
129
|
-
|
130
|
-
|
78
|
+
# Alias for `template_path`
|
79
|
+
def self.path
|
80
|
+
template_path
|
131
81
|
end
|
132
82
|
|
133
|
-
#
|
134
|
-
# corresponding template.
|
83
|
+
# Alias for `template_path`
|
135
84
|
def self.path=(path)
|
136
|
-
|
85
|
+
self.template_path = path
|
137
86
|
end
|
138
87
|
|
139
|
-
|
140
|
-
|
88
|
+
# A Mustache template's default extension is 'html'
|
89
|
+
def self.template_extension
|
90
|
+
@template_extension || 'html'
|
141
91
|
end
|
142
92
|
|
143
|
-
|
144
|
-
|
145
|
-
|
93
|
+
def self.template_extension=(template_extension)
|
94
|
+
@template_extension = template_extension
|
95
|
+
end
|
96
|
+
|
97
|
+
# The template file is the absolute path of the file Mustache will
|
98
|
+
# use as its template. By default it's ./class_name.html
|
146
99
|
def self.template_file
|
147
|
-
@template_file
|
100
|
+
@template_file || "#{template_path}/#{underscore(to_s)}.#{template_extension}"
|
148
101
|
end
|
149
102
|
|
150
103
|
def self.template_file=(template_file)
|
151
104
|
@template_file = template_file
|
152
105
|
end
|
153
106
|
|
107
|
+
# The template is the actual string Mustache uses as its template.
|
108
|
+
# There is a bit of magic here: what we get back is actually a
|
109
|
+
# Mustache::Template object here, but you can still safely use
|
110
|
+
# `template=` with a string.
|
154
111
|
def self.template
|
155
|
-
@template
|
112
|
+
@template || templateify(File.read(template_file))
|
156
113
|
end
|
157
114
|
|
158
|
-
|
159
|
-
|
160
|
-
@template_extension ||= 'html'
|
161
|
-
end
|
162
|
-
|
163
|
-
def self.template_extension=(template_extension)
|
164
|
-
@template_extension = template_extension
|
115
|
+
def self.template=(template)
|
116
|
+
@template = templateify(template)
|
165
117
|
end
|
166
118
|
|
167
119
|
# template_partial => TemplatePartial
|
168
120
|
def self.classify(underscored)
|
169
|
-
underscored.split(/[-_]/).map
|
121
|
+
underscored.split(/[-_]/).map do |part|
|
122
|
+
part[0] = part[0].chr.upcase; part
|
123
|
+
end.join
|
170
124
|
end
|
171
125
|
|
172
126
|
# TemplatePartial => template_partial
|
@@ -176,31 +130,60 @@ class Mustache
|
|
176
130
|
string.gsub(/[A-Z]/) { |s| "_#{s.downcase}"}
|
177
131
|
end
|
178
132
|
|
179
|
-
#
|
180
|
-
|
181
|
-
|
133
|
+
# Turns a string into a Mustache::Template. If passed a Template,
|
134
|
+
# returns it.
|
135
|
+
def self.templateify(obj)
|
136
|
+
if obj.is_a?(Template)
|
137
|
+
obj
|
138
|
+
else
|
139
|
+
Template.new(obj.to_s, template_path, template_extension)
|
140
|
+
end
|
182
141
|
end
|
183
142
|
|
184
|
-
|
185
|
-
|
143
|
+
# Instance version.Turns a string into a Mustache::Template. If passed a Template,
|
144
|
+
def templateify(obj)
|
145
|
+
if obj.is_a?(Template)
|
146
|
+
obj
|
147
|
+
else
|
148
|
+
Template.new(obj.to_s, template_path, template_extension)
|
149
|
+
end
|
186
150
|
end
|
187
151
|
|
188
|
-
#
|
189
|
-
|
190
|
-
|
152
|
+
#
|
153
|
+
# Instance level settings
|
154
|
+
#
|
155
|
+
|
156
|
+
def template_path
|
157
|
+
@template_path || self.class.template_path
|
191
158
|
end
|
192
159
|
|
193
|
-
def
|
194
|
-
@
|
160
|
+
def template_path=(template_path)
|
161
|
+
@template_path = template_path
|
195
162
|
end
|
196
163
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
164
|
+
def template_extension
|
165
|
+
@template_extension || self.class.template_extension
|
166
|
+
end
|
167
|
+
|
168
|
+
def template_extension=(template_extension)
|
169
|
+
@template_extension = template_extension
|
170
|
+
end
|
171
|
+
|
172
|
+
def template_file
|
173
|
+
return @template_file if @template_file
|
174
|
+
"#{template_path}/#{Mustache.underscore(self.class.name)}.#{template_extension}"
|
175
|
+
end
|
176
|
+
|
177
|
+
def template_file=(template_file)
|
178
|
+
@template_file = template_file
|
179
|
+
end
|
180
|
+
|
181
|
+
def template
|
182
|
+
@template || templateify(File.read(template_file))
|
183
|
+
end
|
184
|
+
|
185
|
+
def template=(template)
|
186
|
+
@template = templateify(template)
|
204
187
|
end
|
205
188
|
|
206
189
|
# A helper method which gives access to the context at a given time.
|
@@ -210,7 +193,12 @@ class Mustache
|
|
210
193
|
@context ||= Context.new(self)
|
211
194
|
end
|
212
195
|
|
213
|
-
# Context accessors
|
196
|
+
# Context accessors.
|
197
|
+
#
|
198
|
+
# view = Mustache.new
|
199
|
+
# view[:name] = "Jon"
|
200
|
+
# view.template = "Hi, {{name}}!"
|
201
|
+
# view.render # => "Hi, Jon!"
|
214
202
|
def [](key)
|
215
203
|
context[key.to_sym]
|
216
204
|
end
|
@@ -222,7 +210,7 @@ class Mustache
|
|
222
210
|
# Parses our fancy pants template file and returns normal file with
|
223
211
|
# all special {{tags}} and {{#sections}}replaced{{/sections}}.
|
224
212
|
def render(data = template, ctx = {})
|
225
|
-
|
213
|
+
templateify(data).render(context.update(ctx))
|
226
214
|
end
|
227
215
|
alias_method :to_html, :render
|
228
216
|
alias_method :to_text, :render
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Mustache
|
2
|
+
# A Context represents the context which a Mustache template is
|
3
|
+
# executed within. All Mustache tags reference keys in the Context.
|
4
|
+
class Context < Hash
|
5
|
+
def initialize(mustache)
|
6
|
+
@mustache = mustache
|
7
|
+
super()
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](name)
|
11
|
+
if has_key?(name)
|
12
|
+
super
|
13
|
+
elsif @mustache.respond_to?(name)
|
14
|
+
@mustache.send(name)
|
15
|
+
else
|
16
|
+
raise "Can't find #{name} in #{@mustache.inspect}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
class Mustache
|
4
|
+
# A Template is a compiled version of a Mustache template.
|
5
|
+
#
|
6
|
+
# The idea is this: when handed a Mustache template, convert it into
|
7
|
+
# a Ruby string by transforming Mustache tags into interpolated
|
8
|
+
# Ruby.
|
9
|
+
#
|
10
|
+
# You shouldn't use this class directly.
|
11
|
+
class Template
|
12
|
+
# Expects a Mustache template as a string along with a template
|
13
|
+
# path, which it uses to find partials.
|
14
|
+
def initialize(source, template_path = '.', template_extension = 'html')
|
15
|
+
@source = source
|
16
|
+
@template_path = template_path
|
17
|
+
@template_extension = template_extension
|
18
|
+
@tmpid = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
# Renders the `@source` Mustache template using the given
|
22
|
+
# `context`, which should be a simple hash keyed with symbols.
|
23
|
+
def render(context)
|
24
|
+
# Compile our Mustache template into a Ruby string
|
25
|
+
compiled = "def render(ctx) #{compile} end"
|
26
|
+
|
27
|
+
# Here we rewrite ourself with the interpolated Ruby version of
|
28
|
+
# our Mustache template so subsequent calls are very fast and
|
29
|
+
# can skip the compilation stage.
|
30
|
+
instance_eval(compiled, __FILE__, __LINE__ - 1)
|
31
|
+
|
32
|
+
# Call the newly rewritten version of #render
|
33
|
+
render(context)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Does the dirty work of transforming a Mustache template into an
|
37
|
+
# interpolation-friendly Ruby string.
|
38
|
+
def compile(src = @source)
|
39
|
+
"\"#{compile_sections(src)}\""
|
40
|
+
end
|
41
|
+
|
42
|
+
# {{#sections}}okay{{/sections}}
|
43
|
+
#
|
44
|
+
# Sections can return true, false, or an enumerable.
|
45
|
+
# If true, the section is displayed.
|
46
|
+
# If false, the section is not displayed.
|
47
|
+
# If enumerable, the return value is iterated over (a `for` loop).
|
48
|
+
def compile_sections(src)
|
49
|
+
res = ""
|
50
|
+
while src =~ /^\s*\{\{\#(.+)\}\}\n*(.+)^\s*\{\{\/\1\}\}\n*/m
|
51
|
+
# $` = The string to the left of the last successful match
|
52
|
+
res << compile_tags($`)
|
53
|
+
name = $1.strip.to_sym.inspect
|
54
|
+
code = compile($2)
|
55
|
+
ctxtmp = "ctx#{tmpid}"
|
56
|
+
res << ev("(v = ctx[#{name}]) ? v.respond_to?(:each) ? "\
|
57
|
+
"(#{ctxtmp}=ctx.dup; r=v.map{|h|ctx.update(h);#{code}}.join; "\
|
58
|
+
"ctx.replace(#{ctxtmp});r) : #{code} : ''")
|
59
|
+
# $' = The string to the right of the last successful match
|
60
|
+
src = $'
|
61
|
+
end
|
62
|
+
res << compile_tags(src)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Find and replace all non-section tags.
|
66
|
+
# In particular we look for four types of tags:
|
67
|
+
# 1. Escaped variable tags - {{var}}
|
68
|
+
# 2. Unescaped variable tags - {{{var}}}
|
69
|
+
# 3. Comment variable tags - {{! comment}
|
70
|
+
# 4. Partial tags - {{< partial_name }}
|
71
|
+
def compile_tags(src)
|
72
|
+
res = ""
|
73
|
+
while src =~ /\{\{(!|<|\{)?([^\/#]+?)\1?\}\}+/
|
74
|
+
res << str($`)
|
75
|
+
case $1
|
76
|
+
when '!'
|
77
|
+
# ignore comments
|
78
|
+
when '<'
|
79
|
+
res << compile_partial($2.strip)
|
80
|
+
when '{'
|
81
|
+
res << utag($2.strip)
|
82
|
+
else
|
83
|
+
res << etag($2.strip)
|
84
|
+
end
|
85
|
+
src = $'
|
86
|
+
end
|
87
|
+
res << str(src)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Partials are basically a way to render views from inside other views.
|
91
|
+
def compile_partial(name)
|
92
|
+
klass = Mustache.classify(name)
|
93
|
+
if Object.const_defined?(klass)
|
94
|
+
ev("#{klass}.render")
|
95
|
+
else
|
96
|
+
src = File.read("#{@template_path}/#{name}.#{@template_extension}")
|
97
|
+
compile(src)[1..-2]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Generate a temporary id, used when compiling code.
|
102
|
+
def tmpid
|
103
|
+
@tmpid += 1
|
104
|
+
end
|
105
|
+
|
106
|
+
# Get a (hopefully) literal version of an object, sans quotes
|
107
|
+
def str(s)
|
108
|
+
s.inspect[1..-2]
|
109
|
+
end
|
110
|
+
|
111
|
+
# {{}} - an escaped tag
|
112
|
+
def etag(s)
|
113
|
+
ev("CGI.escapeHTML(ctx[#{s.strip.to_sym.inspect}].to_s)")
|
114
|
+
end
|
115
|
+
|
116
|
+
# {{{}}} - an unescaped tag
|
117
|
+
def utag(s)
|
118
|
+
ev("ctx[#{s.strip.to_sym.inspect}]")
|
119
|
+
end
|
120
|
+
|
121
|
+
# An interpolation-friendly version of a string, for use within a
|
122
|
+
# Ruby string.
|
123
|
+
def ev(s)
|
124
|
+
"#\{#{s}}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/mustache/version.rb
CHANGED
data/test/mustache_test.rb
CHANGED
@@ -23,7 +23,7 @@ end_passenger
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def test_complex_view
|
26
|
-
assert_equal <<-end_complex, ComplexView.
|
26
|
+
assert_equal <<-end_complex, ComplexView.render
|
27
27
|
<h1>Colors</h1>
|
28
28
|
<ul>
|
29
29
|
<li><strong>red</strong></li>
|
@@ -34,7 +34,7 @@ end_complex
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def test_simple
|
37
|
-
assert_equal <<-end_simple, Simple.
|
37
|
+
assert_equal <<-end_simple, Simple.render
|
38
38
|
Hello Chris
|
39
39
|
You have just won $10000!
|
40
40
|
Well, $6000.0, after taxes.
|
@@ -47,7 +47,7 @@ end_simple
|
|
47
47
|
view[:value] = '4000'
|
48
48
|
view[:in_ca] = false
|
49
49
|
|
50
|
-
assert_equal <<-end_simple, view.
|
50
|
+
assert_equal <<-end_simple, view.render
|
51
51
|
Hello Bob
|
52
52
|
You have just won $4000!
|
53
53
|
end_simple
|
@@ -62,7 +62,7 @@ end_simple
|
|
62
62
|
{ :taxed_value => 3 },
|
63
63
|
]
|
64
64
|
|
65
|
-
assert_equal <<-end_simple, view.
|
65
|
+
assert_equal <<-end_simple, view.render
|
66
66
|
Hello Crazy
|
67
67
|
You have just won $10000!
|
68
68
|
Well, $1, after taxes.
|
@@ -76,11 +76,11 @@ end_simple
|
|
76
76
|
view.template = 'Hi {{person}}!'
|
77
77
|
view[:person] = 'mom'
|
78
78
|
|
79
|
-
assert_equal 'Hi mom!', view.
|
79
|
+
assert_equal 'Hi mom!', view.render
|
80
80
|
end
|
81
81
|
|
82
82
|
def test_view_partial
|
83
|
-
assert_equal <<-end_partial.strip, ViewPartial.
|
83
|
+
assert_equal <<-end_partial.strip, ViewPartial.render
|
84
84
|
<h1>Welcome</h1>
|
85
85
|
Hello Chris
|
86
86
|
You have just won $10000!
|
@@ -91,22 +91,34 @@ end_partial
|
|
91
91
|
end
|
92
92
|
|
93
93
|
def test_template_partial
|
94
|
-
assert_equal <<-end_partial.strip, TemplatePartial.
|
94
|
+
assert_equal <<-end_partial.strip, TemplatePartial.render
|
95
95
|
<h1>Welcome</h1>
|
96
96
|
Again, Welcome!
|
97
97
|
end_partial
|
98
98
|
end
|
99
99
|
|
100
|
+
def test_template_partial_with_custom_extension
|
101
|
+
partial = TemplatePartial.new
|
102
|
+
partial.template_extension = 'txt'
|
103
|
+
|
104
|
+
assert_equal <<-end_partial.strip, partial.render.strip
|
105
|
+
Welcome
|
106
|
+
-------
|
107
|
+
|
108
|
+
## Again, Welcome! ##
|
109
|
+
end_partial
|
110
|
+
end
|
111
|
+
|
100
112
|
def test_comments
|
101
|
-
assert_equal "<h1>A Comedy of Errors</h1>\n", Comments.
|
113
|
+
assert_equal "<h1>A Comedy of Errors</h1>\n", Comments.render
|
102
114
|
end
|
103
115
|
|
104
116
|
def test_escaped
|
105
|
-
assert_equal '<h1>Bear > Shark</h1>', Escaped.
|
117
|
+
assert_equal '<h1>Bear > Shark</h1>', Escaped.render
|
106
118
|
end
|
107
119
|
|
108
120
|
def test_unescaped
|
109
|
-
assert_equal '<h1>Bear > Shark</h1>', Unescaped.
|
121
|
+
assert_equal '<h1>Bear > Shark</h1>', Unescaped.render
|
110
122
|
end
|
111
123
|
|
112
124
|
def test_classify
|
@@ -138,9 +150,9 @@ end_partial
|
|
138
150
|
</VirtualHost>
|
139
151
|
data
|
140
152
|
template = File.read("examples/passenger.conf")
|
141
|
-
assert_equal expected, Mustache.render(template, :stage => 'production',
|
142
|
-
:server => 'example.com',
|
153
|
+
assert_equal expected, Mustache.render(template, :stage => 'production',
|
154
|
+
:server => 'example.com',
|
143
155
|
:deploy_to => '/var/www/example.com' )
|
144
156
|
end
|
145
157
|
|
146
|
-
end
|
158
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mustache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Wanstrath
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-10-
|
12
|
+
date: 2009-10-10 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -24,6 +24,7 @@ extra_rdoc_files:
|
|
24
24
|
- README.md
|
25
25
|
files:
|
26
26
|
- .gitignore
|
27
|
+
- .kick
|
27
28
|
- CONTRIBUTORS
|
28
29
|
- LICENSE
|
29
30
|
- README.md
|
@@ -39,18 +40,22 @@ files:
|
|
39
40
|
- examples/escaped.html
|
40
41
|
- examples/escaped.rb
|
41
42
|
- examples/inner_partial.html
|
43
|
+
- examples/inner_partial.txt
|
42
44
|
- examples/passenger.conf
|
43
45
|
- examples/passenger.rb
|
44
46
|
- examples/simple.html
|
45
47
|
- examples/simple.rb
|
46
48
|
- examples/template_partial.html
|
47
49
|
- examples/template_partial.rb
|
50
|
+
- examples/template_partial.txt
|
48
51
|
- examples/unescaped.html
|
49
52
|
- examples/unescaped.rb
|
50
53
|
- examples/view_partial.html
|
51
54
|
- examples/view_partial.rb
|
52
55
|
- lib/mustache.rb
|
56
|
+
- lib/mustache/context.rb
|
53
57
|
- lib/mustache/sinatra.rb
|
58
|
+
- lib/mustache/template.rb
|
54
59
|
- lib/mustache/version.rb
|
55
60
|
- test/mustache_test.rb
|
56
61
|
has_rdoc: true
|