haml 2.0.3 → 2.0.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of haml might be problematic. Click here for more details.
- data/README.rdoc +1 -1
- data/VERSION +1 -1
- data/init.rb +1 -0
- data/lib/haml.rb +24 -19
- data/lib/haml/buffer.rb +8 -5
- data/lib/haml/engine.rb +1 -1
- data/lib/haml/exec.rb +1 -5
- data/lib/haml/filters.rb +3 -2
- data/lib/haml/helpers.rb +53 -32
- data/lib/haml/helpers/action_view_mods.rb +12 -4
- data/lib/haml/html.rb +48 -9
- data/lib/haml/precompiler.rb +11 -4
- data/lib/sass.rb +3 -3
- data/lib/sass/css.rb +1 -1
- data/lib/sass/engine.rb +11 -11
- data/test/benchmark.rb +14 -2
- data/test/haml/engine_test.rb +48 -4
- data/test/haml/helper_test.rb +2 -2
- data/test/haml/html2haml_test.rb +35 -0
- data/test/haml/results/partial_layout.xhtml +5 -0
- data/test/haml/template_test.rb +19 -2
- data/test/haml/templates/_layout_for_partial.haml +3 -0
- data/test/haml/templates/eval_suppressed.haml +1 -1
- data/test/haml/templates/helpers.haml +6 -6
- data/test/haml/templates/partial_layout.haml +3 -0
- data/test/linked_rails.rb +12 -0
- data/test/sass/engine_test.rb +10 -0
- data/test/sass/plugin_test.rb +0 -4
- data/test/test_helper.rb +7 -12
- metadata +7 -3
data/README.rdoc
CHANGED
@@ -12,7 +12,7 @@ and providing elegant, easily understandable, and powerful syntax.
|
|
12
12
|
== Using
|
13
13
|
|
14
14
|
There are several ways to use Haml and Sass.
|
15
|
-
They can be used as a
|
15
|
+
They can be used as a plugin for Rails or Merb,
|
16
16
|
or embedded on their own in other applications.
|
17
17
|
The first step of all of these is to install the Haml gem:
|
18
18
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.0.
|
1
|
+
2.0.4
|
data/init.rb
CHANGED
data/lib/haml.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
dir = File.dirname(__FILE__)
|
2
|
-
$LOAD_PATH
|
2
|
+
$LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
|
3
3
|
|
4
4
|
# = Haml (XHTML Abstraction Markup Language)
|
5
5
|
#
|
@@ -138,7 +138,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
|
138
138
|
#
|
139
139
|
# is compiled to:
|
140
140
|
#
|
141
|
-
# <head name=
|
141
|
+
# <head name='doc_head'>
|
142
142
|
# <script src='javascripts/script_9' type='text/javascript'>
|
143
143
|
# </script>
|
144
144
|
# </head>
|
@@ -159,7 +159,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
|
159
159
|
#
|
160
160
|
# This is compiled to:
|
161
161
|
#
|
162
|
-
# <html lang='fr-fr' xml:lang='fr
|
162
|
+
# <html lang='fr-fr' xml:lang='fr-fr' xmlns='http://www.w3.org/1999/xhtml'>
|
163
163
|
# </html>
|
164
164
|
#
|
165
165
|
# You can use as many such attribute methods as you want
|
@@ -184,6 +184,9 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
|
184
184
|
#
|
185
185
|
# <sandwich bread='whole wheat' delicious='true' filling='peanut butter and jelly' />
|
186
186
|
#
|
187
|
+
# Note that the Haml attributes list has the same syntax as a Ruby method call.
|
188
|
+
# This means that any attribute methods must come before the hash literal.
|
189
|
+
#
|
187
190
|
# ===== Boolean Attributes
|
188
191
|
#
|
189
192
|
# Some attributes, such as "checked" for <tt>input</tt> tags or "selected" for <tt>option</tt> tags,
|
@@ -200,7 +203,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
|
200
203
|
# In XHTML, the only valid value for these attributes is the name of the attribute.
|
201
204
|
# Thus this will render in XHTML as
|
202
205
|
#
|
203
|
-
# <input selected=
|
206
|
+
# <input selected='selected'>
|
204
207
|
#
|
205
208
|
# To set these attributes to false, simply assign them to a Ruby false value.
|
206
209
|
# In both XHTML and HTML
|
@@ -238,8 +241,8 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
|
238
241
|
#
|
239
242
|
# is compiled to:
|
240
243
|
#
|
241
|
-
# <div class=
|
242
|
-
# <bar class=
|
244
|
+
# <div class='greeting_crazy_user' id='greeting_crazy_user_15'>
|
245
|
+
# <bar class='fixnum' id='fixnum_581' />
|
243
246
|
# Hello!
|
244
247
|
# </div>
|
245
248
|
#
|
@@ -306,11 +309,11 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
|
306
309
|
#
|
307
310
|
# is compiled to:
|
308
311
|
#
|
309
|
-
# <div id=
|
310
|
-
# <div class=
|
311
|
-
# <div class=
|
312
|
-
# <div class=
|
313
|
-
# <div class=
|
312
|
+
# <div id='content'>
|
313
|
+
# <div class='articles'>
|
314
|
+
# <div class='article title'>Doogie Howser Comes Out</div>
|
315
|
+
# <div class='article date'>2006-11-05</div>
|
316
|
+
# <div class='article entry'>
|
314
317
|
# Neil Patrick Harris would like to dispel any rumors that he is straight
|
315
318
|
# </div>
|
316
319
|
# </div>
|
@@ -474,7 +477,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
|
474
477
|
#
|
475
478
|
# is compiled to:
|
476
479
|
#
|
477
|
-
# <?xml version=
|
480
|
+
# <?xml version='1.0' encoding='utf-8' ?>
|
478
481
|
# <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
479
482
|
# <html>
|
480
483
|
# <head>
|
@@ -514,7 +517,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
|
514
517
|
#
|
515
518
|
# is compiled to:
|
516
519
|
#
|
517
|
-
# <?xml version=
|
520
|
+
# <?xml version='1.0' encoding='iso-8859-1' ?>
|
518
521
|
#
|
519
522
|
# ==== /
|
520
523
|
#
|
@@ -566,7 +569,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
|
566
569
|
# </a>
|
567
570
|
# <![endif]-->
|
568
571
|
#
|
569
|
-
# ==== \
|
572
|
+
# ==== \
|
570
573
|
#
|
571
574
|
# The backslash character escapes the first character of a line,
|
572
575
|
# allowing use of otherwise interpreted characters as plain text.
|
@@ -915,9 +918,9 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
|
915
918
|
#
|
916
919
|
# Haml::Template.options[:format] = :html5
|
917
920
|
#
|
918
|
-
# ...or by setting the <tt>Merb::
|
921
|
+
# ...or by setting the <tt>Merb::Plugin.config[:haml]</tt> hash in <tt>init.rb</tt> in Merb...
|
919
922
|
#
|
920
|
-
# Merb::
|
923
|
+
# Merb::Plugin.config[:haml][:format] = :html5
|
921
924
|
#
|
922
925
|
# ...or by passing an options hash to Haml::Engine.new.
|
923
926
|
# Available options are:
|
@@ -993,7 +996,7 @@ module Haml
|
|
993
996
|
|
994
997
|
if File.exists?(scope('REVISION'))
|
995
998
|
rev = File.read(scope('REVISION')).strip
|
996
|
-
rev = nil if rev !~
|
999
|
+
rev = nil if rev !~ /^([a-f0-9]+|\(.*\))$/
|
997
1000
|
end
|
998
1001
|
|
999
1002
|
if rev.nil? && File.exists?(scope('.git/HEAD'))
|
@@ -1005,8 +1008,10 @@ module Haml
|
|
1005
1008
|
|
1006
1009
|
if rev
|
1007
1010
|
@@version[:rev] = rev
|
1008
|
-
|
1009
|
-
|
1011
|
+
unless rev[0] == ?(
|
1012
|
+
@@version[:string] << "."
|
1013
|
+
@@version[:string] << rev[0...7]
|
1014
|
+
end
|
1010
1015
|
end
|
1011
1016
|
|
1012
1017
|
@@version
|
data/lib/haml/buffer.rb
CHANGED
@@ -151,16 +151,19 @@ module Haml
|
|
151
151
|
tabulation = @real_tabs
|
152
152
|
|
153
153
|
attributes = class_id
|
154
|
-
attributes_hashes.each do |
|
155
|
-
|
156
|
-
self.class.merge_attrs(attributes, attributes_hash)
|
154
|
+
attributes_hashes.each do |old|
|
155
|
+
self.class.merge_attrs(attributes, old.inject({}) {|h, (key, val)| h[key.to_s] = val; h})
|
157
156
|
end
|
158
157
|
self.class.merge_attrs(attributes, parse_object_ref(obj_ref)) if obj_ref
|
159
158
|
|
160
|
-
if self_closing
|
159
|
+
if self_closing && xhtml?
|
161
160
|
str = " />" + (nuke_outer_whitespace ? "" : "\n")
|
162
161
|
else
|
163
|
-
str = ">" + (
|
162
|
+
str = ">" + ((if self_closing && html?
|
163
|
+
nuke_outer_whitespace
|
164
|
+
else
|
165
|
+
try_one_line || preserve_tag || nuke_inner_whitespace
|
166
|
+
end) ? "" : "\n")
|
164
167
|
end
|
165
168
|
|
166
169
|
attributes = Precompiler.build_attributes(html?, @options[:attr_wrapper], attributes)
|
data/lib/haml/engine.rb
CHANGED
@@ -92,7 +92,7 @@ module Haml
|
|
92
92
|
if @options[:filters]
|
93
93
|
warn <<END
|
94
94
|
DEPRECATION WARNING:
|
95
|
-
The Haml :filters option is deprecated and will be removed in version 2.
|
95
|
+
The Haml :filters option is deprecated and will be removed in version 2.2.
|
96
96
|
Filters are now automatically registered.
|
97
97
|
END
|
98
98
|
end
|
data/lib/haml/exec.rb
CHANGED
@@ -24,13 +24,9 @@ module Haml
|
|
24
24
|
|
25
25
|
@options
|
26
26
|
rescue Exception => e
|
27
|
-
raise e if e.is_a?
|
27
|
+
raise e if @options[:trace] || e.is_a?(SystemExit)
|
28
28
|
|
29
|
-
$stderr.print "#{e.class} on line #{get_line e}: " if @options[:trace]
|
30
29
|
$stderr.puts e.message
|
31
|
-
|
32
|
-
e.backtrace[1..-1].each { |t| $stderr.puts " #{t}" } if @options[:trace]
|
33
|
-
|
34
30
|
exit 1
|
35
31
|
end
|
36
32
|
exit 0
|
data/lib/haml/filters.rb
CHANGED
@@ -212,10 +212,10 @@ END
|
|
212
212
|
|
213
213
|
module Sass
|
214
214
|
include Base
|
215
|
-
lazy_require 'sass/
|
215
|
+
lazy_require 'sass/plugin'
|
216
216
|
|
217
217
|
def render(text)
|
218
|
-
::Sass::Engine.new(text
|
218
|
+
::Sass::Engine.new(text, ::Sass::Plugin.engine_options).render
|
219
219
|
end
|
220
220
|
end
|
221
221
|
|
@@ -239,6 +239,7 @@ END
|
|
239
239
|
end
|
240
240
|
end
|
241
241
|
RedCloth = Textile
|
242
|
+
Filters.defined['redcloth'] = RedCloth
|
242
243
|
|
243
244
|
# Uses BlueCloth or RedCloth to provide only Markdown (not Textile) parsing
|
244
245
|
module Markdown
|
data/lib/haml/helpers.rb
CHANGED
@@ -254,11 +254,39 @@ module Haml
|
|
254
254
|
# the local variable <tt>foo</tt> would be assigned to "<p>13</p>\n".
|
255
255
|
#
|
256
256
|
def capture_haml(*args, &block)
|
257
|
-
|
257
|
+
buffer = eval('_hamlout', block) rescue haml_buffer
|
258
|
+
with_haml_buffer(buffer) do
|
259
|
+
position = haml_buffer.buffer.length
|
260
|
+
|
261
|
+
block.call(*args)
|
262
|
+
|
263
|
+
captured = haml_buffer.buffer.slice!(position..-1)
|
264
|
+
|
265
|
+
min_tabs = nil
|
266
|
+
captured.each do |line|
|
267
|
+
tabs = line.index(/[^ ]/)
|
268
|
+
min_tabs ||= tabs
|
269
|
+
min_tabs = min_tabs > tabs ? tabs : min_tabs
|
270
|
+
end
|
271
|
+
|
272
|
+
result = captured.map do |line|
|
273
|
+
line[min_tabs..-1]
|
274
|
+
end
|
275
|
+
result.to_s
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def puts(*args) # :nodoc:
|
280
|
+
warn <<END
|
281
|
+
DEPRECATION WARNING:
|
282
|
+
The Haml #puts helper is deprecated and will be removed in version 2.4.
|
283
|
+
Use the #haml_concat helper instead.
|
284
|
+
END
|
285
|
+
haml_concat *args
|
258
286
|
end
|
259
287
|
|
260
288
|
# Outputs text directly to the Haml buffer, with the proper tabulation
|
261
|
-
def
|
289
|
+
def haml_concat(text = "")
|
262
290
|
haml_buffer.buffer << (' ' * haml_buffer.tabulation) << text.to_s << "\n"
|
263
291
|
nil
|
264
292
|
end
|
@@ -271,7 +299,7 @@ module Haml
|
|
271
299
|
# Creates an HTML tag with the given name and optionally text and attributes.
|
272
300
|
# Can take a block that will be executed
|
273
301
|
# between when the opening and closing tags are output.
|
274
|
-
# If the block is a Haml block or outputs text using
|
302
|
+
# If the block is a Haml block or outputs text using haml_concat,
|
275
303
|
# the text will be properly indented.
|
276
304
|
#
|
277
305
|
# <tt>flags</tt> is a list of symbol flags
|
@@ -285,10 +313,10 @@ module Haml
|
|
285
313
|
# haml_tag :tr do
|
286
314
|
# haml_tag :td, {:class => 'cell'} do
|
287
315
|
# haml_tag :strong, "strong!"
|
288
|
-
#
|
316
|
+
# haml_concat "data"
|
289
317
|
# end
|
290
318
|
# haml_tag :td do
|
291
|
-
#
|
319
|
+
# haml_concat "more_data"
|
292
320
|
# end
|
293
321
|
# end
|
294
322
|
# end
|
@@ -319,7 +347,7 @@ module Haml
|
|
319
347
|
rest.shift || {})
|
320
348
|
|
321
349
|
if text.nil? && block.nil? && (haml_buffer.options[:autoclose].include?(name) || flags.include?(:/))
|
322
|
-
|
350
|
+
haml_concat "<#{name}#{attributes} />"
|
323
351
|
return nil
|
324
352
|
end
|
325
353
|
|
@@ -331,7 +359,7 @@ module Haml
|
|
331
359
|
tag = "<#{name}#{attributes}>"
|
332
360
|
if block.nil?
|
333
361
|
tag << text.to_s << "</#{name}>"
|
334
|
-
|
362
|
+
haml_concat tag
|
335
363
|
return
|
336
364
|
end
|
337
365
|
|
@@ -341,15 +369,15 @@ module Haml
|
|
341
369
|
|
342
370
|
if flags.include?(:<)
|
343
371
|
tag << capture_haml(&block).strip << "</#{name}>"
|
344
|
-
|
372
|
+
haml_concat tag
|
345
373
|
return
|
346
374
|
end
|
347
375
|
|
348
|
-
|
376
|
+
haml_concat tag
|
349
377
|
tab_up
|
350
378
|
block.call
|
351
379
|
tab_down
|
352
|
-
|
380
|
+
haml_concat "</#{name}>"
|
353
381
|
nil
|
354
382
|
end
|
355
383
|
|
@@ -379,6 +407,21 @@ module Haml
|
|
379
407
|
|
380
408
|
private
|
381
409
|
|
410
|
+
# call-seq:
|
411
|
+
# with_haml_buffer(buffer) {...}
|
412
|
+
#
|
413
|
+
# Runs the block with the given buffer as the currently active buffer.
|
414
|
+
def with_haml_buffer(buffer)
|
415
|
+
@haml_buffer, old_buffer = buffer, @haml_buffer
|
416
|
+
old_buffer.active, was_active = false, old_buffer.active? if old_buffer
|
417
|
+
@haml_buffer.active = true
|
418
|
+
yield
|
419
|
+
ensure
|
420
|
+
@haml_buffer.active = false
|
421
|
+
old_buffer.active = was_active if old_buffer
|
422
|
+
@haml_buffer = old_buffer
|
423
|
+
end
|
424
|
+
|
382
425
|
# Gets a reference to the current Haml::Buffer object.
|
383
426
|
def haml_buffer
|
384
427
|
@haml_buffer
|
@@ -392,28 +435,6 @@ module Haml
|
|
392
435
|
proc { |*args| proc.call(*args) }
|
393
436
|
end
|
394
437
|
|
395
|
-
# Performs the function of capture_haml, assuming <tt>local_buffer</tt>
|
396
|
-
# is where the output of block goes.
|
397
|
-
def capture_haml_with_buffer(local_buffer, *args, &block)
|
398
|
-
position = local_buffer.length
|
399
|
-
|
400
|
-
block.call(*args)
|
401
|
-
|
402
|
-
captured = local_buffer.slice!(position..-1)
|
403
|
-
|
404
|
-
min_tabs = nil
|
405
|
-
captured.each do |line|
|
406
|
-
tabs = line.index(/[^ ]/)
|
407
|
-
min_tabs ||= tabs
|
408
|
-
min_tabs = min_tabs > tabs ? tabs : min_tabs
|
409
|
-
end
|
410
|
-
|
411
|
-
result = captured.map do |line|
|
412
|
-
line[min_tabs..-1]
|
413
|
-
end
|
414
|
-
result.to_s
|
415
|
-
end
|
416
|
-
|
417
438
|
include ActionViewExtensions if self.const_defined? "ActionViewExtensions"
|
418
439
|
end
|
419
440
|
end
|
@@ -2,7 +2,15 @@ if defined?(ActionView) and not defined?(Merb::Plugins)
|
|
2
2
|
module ActionView
|
3
3
|
class Base # :nodoc:
|
4
4
|
def render_with_haml(*args, &block)
|
5
|
-
|
5
|
+
options = args.first
|
6
|
+
|
7
|
+
# If render :layout is used with a block,
|
8
|
+
# it concats rather than returning a string
|
9
|
+
# so we need it to keep thinking it's Haml
|
10
|
+
# until it hits the sub-render
|
11
|
+
if is_haml? && !(options.is_a?(Hash) && options[:layout] && block_given?)
|
12
|
+
return non_haml { render_without_haml(*args, &block) }
|
13
|
+
end
|
6
14
|
render_without_haml(*args, &block)
|
7
15
|
end
|
8
16
|
alias_method :render_without_haml, :render
|
@@ -60,11 +68,11 @@ if defined?(ActionView) and not defined?(Merb::Plugins)
|
|
60
68
|
alias_method :capture_without_haml, :capture
|
61
69
|
alias_method :capture, :capture_with_haml
|
62
70
|
|
63
|
-
def capture_erb_with_buffer_with_haml(*args, &block)
|
71
|
+
def capture_erb_with_buffer_with_haml(buffer, *args, &block)
|
64
72
|
if is_haml?
|
65
|
-
|
73
|
+
capture_haml(*args, &block)
|
66
74
|
else
|
67
|
-
capture_erb_with_buffer_without_haml(*args, &block)
|
75
|
+
capture_erb_with_buffer_without_haml(buffer, *args, &block)
|
68
76
|
end
|
69
77
|
end
|
70
78
|
alias_method :capture_erb_with_buffer_without_haml, :capture_erb_with_buffer
|
data/lib/haml/html.rb
CHANGED
@@ -25,7 +25,7 @@ module Haml
|
|
25
25
|
|
26
26
|
if @@options[:rhtml]
|
27
27
|
match_to_html(template, /<%=(.*?)-?%>/m, 'loud')
|
28
|
-
match_to_html(template,
|
28
|
+
match_to_html(template, /<%-?(.*?)-?%>/m, 'silent')
|
29
29
|
end
|
30
30
|
|
31
31
|
method = @@options[:xhtml] ? Hpricot.method(:XML) : method(:Hpricot)
|
@@ -131,17 +131,20 @@ module Haml
|
|
131
131
|
def to_haml(tabs = 0)
|
132
132
|
output = "#{tabulate(tabs)}"
|
133
133
|
if HTML.options[:rhtml] && name[0...5] == 'haml:'
|
134
|
-
return output + HTML.send("haml_tag_#{name[5..-1]}",
|
135
|
-
CGI.unescapeHTML(self.innerHTML))
|
134
|
+
return output + HTML.send("haml_tag_#{name[5..-1]}", CGI.unescapeHTML(self.inner_text))
|
136
135
|
end
|
137
136
|
|
138
|
-
output += "%#{name}" unless name == 'div' && (
|
137
|
+
output += "%#{name}" unless name == 'div' && (static_id? || static_classname?)
|
139
138
|
|
140
139
|
if attributes
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
140
|
+
if static_id?
|
141
|
+
output += "##{attributes['id']}"
|
142
|
+
remove_attribute('id')
|
143
|
+
end
|
144
|
+
if static_classname?
|
145
|
+
attributes['class'].split(' ').each { |c| output += ".#{c}" }
|
146
|
+
remove_attribute('class')
|
147
|
+
end
|
145
148
|
output += haml_attributes if attributes.length > 0
|
146
149
|
end
|
147
150
|
|
@@ -156,13 +159,49 @@ module Haml
|
|
156
159
|
end
|
157
160
|
|
158
161
|
private
|
162
|
+
|
163
|
+
def dynamic_attributes
|
164
|
+
@dynamic_attributes ||= begin
|
165
|
+
attributes.inject({}) do |dynamic, pair|
|
166
|
+
name, value = pair
|
167
|
+
unless value.empty?
|
168
|
+
full_match = nil
|
169
|
+
ruby_value = value.gsub(%r{<haml:loud>\s*(.+?)\s*</haml:loud>}) do
|
170
|
+
full_match = $`.empty? && $'.empty?
|
171
|
+
full_match ? $1: "\#{#{$1}}"
|
172
|
+
end
|
173
|
+
unless ruby_value == value
|
174
|
+
dynamic[name] = full_match ? ruby_value : %("#{ruby_value}")
|
175
|
+
end
|
176
|
+
end
|
177
|
+
dynamic
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def static_attribute?(name)
|
183
|
+
attributes[name] and !dynamic_attribute?(name)
|
184
|
+
end
|
185
|
+
|
186
|
+
def dynamic_attribute?(name)
|
187
|
+
HTML.options[:rhtml] and dynamic_attributes.key?(name)
|
188
|
+
end
|
189
|
+
|
190
|
+
def static_id?
|
191
|
+
static_attribute? 'id'
|
192
|
+
end
|
193
|
+
|
194
|
+
def static_classname?
|
195
|
+
static_attribute? 'class'
|
196
|
+
end
|
159
197
|
|
160
198
|
# Returns a string representation of an attributes hash
|
161
199
|
# that's prettier than that produced by Hash#inspect
|
162
200
|
def haml_attributes
|
163
201
|
attrs = attributes.map do |name, value|
|
202
|
+
value = dynamic_attribute?(name) ? dynamic_attributes[name] : value.inspect
|
164
203
|
name = name.index(/\W/) ? name.inspect : ":#{name}"
|
165
|
-
"#{name} => #{value
|
204
|
+
"#{name} => #{value}"
|
166
205
|
end
|
167
206
|
"{ #{attrs.join(', ')} }"
|
168
207
|
end
|
data/lib/haml/precompiler.rb
CHANGED
@@ -209,9 +209,11 @@ END
|
|
209
209
|
|
210
210
|
push_silent(text[1..-1], true)
|
211
211
|
newline_now
|
212
|
-
|
213
|
-
|
214
|
-
|
212
|
+
|
213
|
+
case_stmt = text[1..-1].split(' ', 2)[0] == "case"
|
214
|
+
block = @block_opened && !mid_block_keyword?(text)
|
215
|
+
push_and_tabulate([:script]) if block || case_stmt
|
216
|
+
push_and_tabulate(nil) if block && case_stmt
|
215
217
|
when FILTER; start_filtered(text[1..-1].downcase)
|
216
218
|
when DOCTYPE
|
217
219
|
return render_doctype(text) if text[0...3] == '!!!'
|
@@ -371,6 +373,7 @@ END
|
|
371
373
|
when :loud; close_loud value
|
372
374
|
when :filtered; close_filtered value
|
373
375
|
when :haml_comment; close_haml_comment
|
376
|
+
when nil; close_nil
|
374
377
|
end
|
375
378
|
end
|
376
379
|
|
@@ -420,6 +423,10 @@ END
|
|
420
423
|
@template_tabs -= 1
|
421
424
|
end
|
422
425
|
|
426
|
+
def close_nil
|
427
|
+
@template_tabs -= 1
|
428
|
+
end
|
429
|
+
|
423
430
|
# Iterates through the classes and ids supplied through <tt>.</tt>
|
424
431
|
# and <tt>#</tt> syntax, and returns a hash with them as attributes,
|
425
432
|
# that can then be merged with another attributes hash.
|
@@ -544,7 +551,7 @@ END
|
|
544
551
|
preserve_tag &&= !options[:ugly]
|
545
552
|
|
546
553
|
case action
|
547
|
-
when '/'; self_closing =
|
554
|
+
when '/'; self_closing = true
|
548
555
|
when '~'; parse = preserve_script = true
|
549
556
|
when '='
|
550
557
|
parse = true
|
data/lib/sass.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
dir = File.dirname(__FILE__)
|
2
|
-
$LOAD_PATH
|
2
|
+
$LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
|
3
3
|
|
4
4
|
# = Sass (Syntactically Awesome StyleSheets)
|
5
5
|
#
|
@@ -788,9 +788,9 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
|
788
788
|
#
|
789
789
|
# Sass::Plugin.options[:style] = :compact
|
790
790
|
#
|
791
|
-
# ...or by setting the <tt>Merb::
|
791
|
+
# ...or by setting the <tt>Merb::Plugin.config[:sass]</tt> hash in <tt>init.rb</tt> in Merb...
|
792
792
|
#
|
793
|
-
# Merb::
|
793
|
+
# Merb::Plugin.config[:sass][:style] = :compact
|
794
794
|
#
|
795
795
|
# ...or by passing an options hash to Sass::Engine.new.
|
796
796
|
# Available options are:
|
data/lib/sass/css.rb
CHANGED
@@ -237,7 +237,7 @@ module Sass
|
|
237
237
|
root.children.map! do |child|
|
238
238
|
next child unless Tree::RuleNode === child && child.rule.include?(',')
|
239
239
|
child.rule.split(',').map do |rule|
|
240
|
-
node = Tree::RuleNode.new(rule, nil)
|
240
|
+
node = Tree::RuleNode.new(rule.strip, nil)
|
241
241
|
node.children = child.children
|
242
242
|
node
|
243
243
|
end
|
data/lib/sass/engine.rb
CHANGED
@@ -278,7 +278,13 @@ END
|
|
278
278
|
def parse_line(line)
|
279
279
|
case line[0]
|
280
280
|
when ATTRIBUTE_CHAR
|
281
|
-
|
281
|
+
if line[1] != ATTRIBUTE_CHAR
|
282
|
+
parse_attribute(line, ATTRIBUTE)
|
283
|
+
else
|
284
|
+
# Support CSS3-style pseudo-elements,
|
285
|
+
# which begin with ::
|
286
|
+
Tree::RuleNode.new(line, @options[:style])
|
287
|
+
end
|
282
288
|
when Constant::CONSTANT_CHAR
|
283
289
|
parse_constant(line)
|
284
290
|
when COMMENT_CHAR
|
@@ -365,7 +371,7 @@ END
|
|
365
371
|
end
|
366
372
|
|
367
373
|
def parse_mixin_definition(line)
|
368
|
-
mixin_name = line[1..-1]
|
374
|
+
mixin_name = line[1..-1].strip
|
369
375
|
@mixins[mixin_name] = []
|
370
376
|
index = @line
|
371
377
|
line, tabs = @lines[index]
|
@@ -440,15 +446,9 @@ END
|
|
440
446
|
|
441
447
|
new_filename = find_full_path("#{filename}.sass", load_paths)
|
442
448
|
|
443
|
-
if new_filename
|
444
|
-
|
445
|
-
|
446
|
-
else
|
447
|
-
return filename + '.css'
|
448
|
-
end
|
449
|
-
else
|
450
|
-
new_filename
|
451
|
-
end
|
449
|
+
return new_filename if new_filename
|
450
|
+
return filename + '.css' unless was_sass
|
451
|
+
raise SyntaxError.new("File to import not found or unreadable: #{original_filename}.", @line)
|
452
452
|
end
|
453
453
|
|
454
454
|
def self.find_full_path(filename, load_paths)
|
data/test/benchmark.rb
CHANGED
@@ -12,6 +12,7 @@ END
|
|
12
12
|
end
|
13
13
|
|
14
14
|
require File.dirname(__FILE__) + '/../lib/haml'
|
15
|
+
require File.dirname(__FILE__) + '/linked_rails'
|
15
16
|
%w[sass rubygems erb erubis markaby active_support action_controller
|
16
17
|
action_view haml/template].each(&method(:require))
|
17
18
|
|
@@ -23,6 +24,17 @@ rescue LoadError
|
|
23
24
|
raise "The Haml benchmarks require the benchwarmer gem, available from http://github.com/wycats/benchwarmer"
|
24
25
|
end
|
25
26
|
|
27
|
+
def view
|
28
|
+
unless ActionView::Base.instance_methods.include? 'finder'
|
29
|
+
return ActionView::Base.new(File.dirname(__FILE__), vars)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Rails >=2.1.0
|
33
|
+
base = ActionView::Base.new
|
34
|
+
base.finder.append_view_path(File.dirname(__FILE__))
|
35
|
+
base
|
36
|
+
end
|
37
|
+
|
26
38
|
Benchmark.warmer(times) do
|
27
39
|
columns :haml, :erb, :erubis, :mab
|
28
40
|
titles :haml => "Haml", :erb => "ERB", :erubis => "Erubis", :mab => "Markaby"
|
@@ -53,7 +65,7 @@ Benchmark.warmer(times) do
|
|
53
65
|
end
|
54
66
|
|
55
67
|
report "ActionView" do
|
56
|
-
@base =
|
68
|
+
@base = view
|
57
69
|
|
58
70
|
# To cache the template
|
59
71
|
@base.render 'haml/templates/standard'
|
@@ -64,7 +76,7 @@ Benchmark.warmer(times) do
|
|
64
76
|
end
|
65
77
|
|
66
78
|
report "ActionView with deep partials" do
|
67
|
-
@base =
|
79
|
+
@base = view
|
68
80
|
|
69
81
|
# To cache the template
|
70
82
|
@base.render 'haml/templates/action_view'
|
data/test/haml/engine_test.rb
CHANGED
@@ -135,6 +135,20 @@ END
|
|
135
135
|
assert_equal("<img alt='' src='/foo.png' />\n", render("%img{:width => nil, :src => '/foo.png', :alt => String.new}"))
|
136
136
|
end
|
137
137
|
|
138
|
+
def test_attr_hashes_not_modified
|
139
|
+
hash = {:color => 'red'}
|
140
|
+
assert_equal(<<HTML, render(<<HAML, :locals => {:hash => hash}))
|
141
|
+
<div color='red'></div>
|
142
|
+
<div class='special' color='red'></div>
|
143
|
+
<div color='red'></div>
|
144
|
+
HTML
|
145
|
+
%div{hash}
|
146
|
+
.special{hash}
|
147
|
+
%div{hash}
|
148
|
+
HAML
|
149
|
+
assert_equal(hash, {:color => 'red'})
|
150
|
+
end
|
151
|
+
|
138
152
|
def test_end_of_file_multiline
|
139
153
|
assert_equal("<p>0</p>\n<p>1</p>\n<p>2</p>\n", render("- for i in (0...3)\n %p= |\n i |"))
|
140
154
|
end
|
@@ -184,6 +198,33 @@ RESULT
|
|
184
198
|
SOURCE
|
185
199
|
end
|
186
200
|
|
201
|
+
# Mostly a regression test
|
202
|
+
def test_both_case_indentation_work_with_deeply_nested_code
|
203
|
+
result = <<RESULT
|
204
|
+
<h2>
|
205
|
+
other
|
206
|
+
</h2>
|
207
|
+
RESULT
|
208
|
+
assert_equal(result, render(<<HAML))
|
209
|
+
- case 'other'
|
210
|
+
- when 'test'
|
211
|
+
%h2
|
212
|
+
hi
|
213
|
+
- when 'other'
|
214
|
+
%h2
|
215
|
+
other
|
216
|
+
HAML
|
217
|
+
assert_equal(result, render(<<HAML))
|
218
|
+
- case 'other'
|
219
|
+
- when 'test'
|
220
|
+
%h2
|
221
|
+
hi
|
222
|
+
- when 'other'
|
223
|
+
%h2
|
224
|
+
other
|
225
|
+
HAML
|
226
|
+
end
|
227
|
+
|
187
228
|
# HTML escaping tests
|
188
229
|
|
189
230
|
def test_ampersand_equals_should_escape
|
@@ -287,11 +328,11 @@ SOURCE
|
|
287
328
|
|
288
329
|
def test_stop_eval
|
289
330
|
assert_equal("", render("= 'Hello'", :suppress_eval => true))
|
290
|
-
assert_equal("", render("-
|
331
|
+
assert_equal("", render("- haml_concat 'foo'", :suppress_eval => true))
|
291
332
|
assert_equal("<div id='foo' yes='no' />\n", render("#foo{:yes => 'no'}/", :suppress_eval => true))
|
292
333
|
assert_equal("<div id='foo' />\n", render("#foo{:yes => 'no', :call => a_function() }/", :suppress_eval => true))
|
293
334
|
assert_equal("<div />\n", render("%div[1]/", :suppress_eval => true))
|
294
|
-
assert_equal("", render(":ruby\n puts 'hello'", :suppress_eval => true))
|
335
|
+
assert_equal("", render(":ruby\n Kernel.puts 'hello'", :suppress_eval => true))
|
295
336
|
end
|
296
337
|
|
297
338
|
def test_attr_wrapper
|
@@ -540,8 +581,11 @@ END
|
|
540
581
|
assert_equal "<div class='foo'></div>\n", render(".foo", :format => :html4)
|
541
582
|
end
|
542
583
|
|
543
|
-
def
|
544
|
-
assert_equal "<a
|
584
|
+
def test_html_doesnt_add_slash_to_self_closing_tags
|
585
|
+
assert_equal "<a>\n", render("%a/", :format => :html4)
|
586
|
+
assert_equal "<a foo='2'>\n", render("%a{:foo => 1 + 1}/", :format => :html4)
|
587
|
+
assert_equal "<meta>\n", render("%meta", :format => :html4)
|
588
|
+
assert_equal "<meta foo='2'>\n", render("%meta{:foo => 1 + 1}", :format => :html4)
|
545
589
|
end
|
546
590
|
|
547
591
|
def test_html_ignores_xml_prolog_declaration
|
data/test/haml/helper_test.rb
CHANGED
@@ -148,7 +148,7 @@ class HelperTest < Test::Unit::TestCase
|
|
148
148
|
Haml::Helpers.module_eval do
|
149
149
|
def trc(collection, &block)
|
150
150
|
collection.each do |record|
|
151
|
-
|
151
|
+
haml_concat capture_haml(record, &block)
|
152
152
|
end
|
153
153
|
end
|
154
154
|
end
|
@@ -175,7 +175,7 @@ class HelperTest < Test::Unit::TestCase
|
|
175
175
|
|
176
176
|
result = context.capture_haml do
|
177
177
|
context.haml_tag :p, :attr => "val" do
|
178
|
-
context.
|
178
|
+
context.haml_concat "Blah"
|
179
179
|
end
|
180
180
|
end
|
181
181
|
|
data/test/haml/html2haml_test.rb
CHANGED
@@ -40,6 +40,41 @@ class Html2HamlTest < Test::Unit::TestCase
|
|
40
40
|
assert_equal '= h @item.title', render_rhtml('<%=h @item.title %>')
|
41
41
|
assert_equal '= h @item.title', render_rhtml('<%=h @item.title -%>')
|
42
42
|
end
|
43
|
+
|
44
|
+
def test_rhtml_with_html_special_chars
|
45
|
+
assert_equal '= 3 < 5 ? "OK" : "Your computer is b0rken"',
|
46
|
+
render_rhtml(%Q{<%= 3 < 5 ? "OK" : "Your computer is b0rken" %>})
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_rhtml_in_class_attribute
|
50
|
+
assert_equal "%div{ :class => dyna_class }\n I have a dynamic attribute",
|
51
|
+
render_rhtml(%Q{<div class="<%= dyna_class %>">I have a dynamic attribute</div>})
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_rhtml_in_id_attribute
|
55
|
+
assert_equal "%div{ :id => dyna_id }\n I have a dynamic attribute",
|
56
|
+
render_rhtml(%Q{<div id="<%= dyna_id %>">I have a dynamic attribute</div>})
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_rhtml_in_attribute_results_in_string_interpolation
|
60
|
+
assert_equal %(%div{ :id => "item_\#{i}" }\n Ruby string interpolation FTW),
|
61
|
+
render_rhtml(%Q{<div id="item_<%= i %>">Ruby string interpolation FTW</div>})
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_rhtml_in_attribute_with_trailing_content
|
65
|
+
assert_equal %(%div{ :class => "\#{12}!" }\n Bang!),
|
66
|
+
render_rhtml(%Q{<div class="<%= 12 %>!">Bang!</div>})
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_rhtml_in_attribute_to_multiple_interpolations
|
70
|
+
assert_equal %(%div{ :class => "\#{12} + \#{13}" }\n Math is super),
|
71
|
+
render_rhtml(%Q{<div class="<%= 12 %> + <%= 13 %>">Math is super</div>})
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_whitespace_eating_erb_tags
|
75
|
+
assert_equal %(- form_for),
|
76
|
+
render_rhtml(%Q{<%- form_for -%>})
|
77
|
+
end
|
43
78
|
|
44
79
|
protected
|
45
80
|
|
data/test/haml/template_test.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require File.dirname(__FILE__) + '/../test_helper'
|
3
3
|
require 'haml/template'
|
4
|
+
require 'sass/plugin'
|
4
5
|
require File.dirname(__FILE__) + '/mocks/article'
|
5
6
|
|
6
7
|
module Haml::Filters::Test
|
@@ -17,11 +18,17 @@ module Haml::Helpers
|
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
21
|
+
class DummyController
|
22
|
+
def self.controller_path
|
23
|
+
''
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
20
27
|
class TemplateTest < Test::Unit::TestCase
|
21
28
|
TEMPLATE_PATH = File.join(File.dirname(__FILE__), "templates")
|
22
29
|
TEMPLATES = %w{ very_basic standard helpers
|
23
30
|
whitespace_handling original_engine list helpful
|
24
|
-
silent_script tag_parsing just_stuff partials
|
31
|
+
silent_script tag_parsing just_stuff partials partial_layout
|
25
32
|
filters nuke_outer_whitespace nuke_inner_whitespace }
|
26
33
|
|
27
34
|
def setup
|
@@ -35,11 +42,21 @@ class TemplateTest < Test::Unit::TestCase
|
|
35
42
|
@base.finder.append_view_path(TEMPLATE_PATH)
|
36
43
|
end
|
37
44
|
|
38
|
-
@base.
|
45
|
+
if @base.private_methods.include?('evaluate_assigns')
|
46
|
+
@base.send(:evaluate_assigns)
|
47
|
+
else
|
48
|
+
# Rails 2.2
|
49
|
+
@base.send(:_evaluate_assigns_and_ivars)
|
50
|
+
end
|
39
51
|
|
40
52
|
# This is used by form_for.
|
41
53
|
# It's usually provided by ActionController::Base.
|
42
54
|
def @base.protect_against_forgery?; false; end
|
55
|
+
|
56
|
+
# filters template uses :sass
|
57
|
+
Sass::Plugin.options.update(:line_comments => true, :style => :compact)
|
58
|
+
|
59
|
+
@base.controller = DummyController.new
|
43
60
|
end
|
44
61
|
|
45
62
|
def render(text)
|
@@ -52,18 +52,18 @@ click
|
|
52
52
|
= list_of({:google => 'http://www.google.com'}) do |name, link|
|
53
53
|
%a{ :href => link }= name
|
54
54
|
%p
|
55
|
-
-
|
55
|
+
- haml_concat "foo"
|
56
56
|
%div
|
57
|
-
-
|
58
|
-
-
|
57
|
+
- haml_concat "bar"
|
58
|
+
- haml_concat "boom"
|
59
59
|
baz
|
60
|
-
-
|
60
|
+
- haml_concat "boom, again"
|
61
61
|
- haml_tag :table do
|
62
62
|
- haml_tag :tr do
|
63
63
|
- haml_tag :td, {:class => 'cell'} do
|
64
64
|
- haml_tag :strong, "strong!"
|
65
|
-
-
|
65
|
+
- haml_concat "data"
|
66
66
|
- haml_tag :td do
|
67
|
-
-
|
67
|
+
- haml_concat "more_data"
|
68
68
|
- haml_tag :hr
|
69
69
|
- haml_tag :div, ''
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# allows testing with edge Rails by creating a test/rails symlink
|
2
|
+
linked_rails = File.dirname(__FILE__) + '/rails'
|
3
|
+
|
4
|
+
if File.exists?(linked_rails) && !$:.include?(linked_rails + '/activesupport/lib')
|
5
|
+
puts "[ using linked Rails ]"
|
6
|
+
$:.unshift linked_rails + '/activesupport/lib'
|
7
|
+
$:.unshift linked_rails + '/actionpack/lib'
|
8
|
+
else
|
9
|
+
require 'rubygems'
|
10
|
+
end
|
11
|
+
require 'action_controller'
|
12
|
+
require 'action_view'
|
data/test/sass/engine_test.rb
CHANGED
@@ -174,6 +174,16 @@ END
|
|
174
174
|
end
|
175
175
|
end
|
176
176
|
|
177
|
+
def test_pseudo_elements
|
178
|
+
assert_equal(<<CSS, render(<<SASS))
|
179
|
+
::first-line {
|
180
|
+
size: 10em; }
|
181
|
+
CSS
|
182
|
+
::first-line
|
183
|
+
size: 10em
|
184
|
+
SASS
|
185
|
+
end
|
186
|
+
|
177
187
|
def test_directive
|
178
188
|
assert_equal("@a b;", render("@a b"))
|
179
189
|
|
data/test/sass/plugin_test.rb
CHANGED
data/test/test_helper.rb
CHANGED
@@ -1,18 +1,13 @@
|
|
1
1
|
lib_dir = File.dirname(__FILE__) + '/../lib'
|
2
|
-
|
3
|
-
linked_rails = File.dirname(__FILE__) + '/rails'
|
4
|
-
|
5
|
-
if File.exists?(linked_rails) && !$:.include?(linked_rails + '/activesupport/lib')
|
6
|
-
puts "[ using linked Rails ]"
|
7
|
-
$:.unshift linked_rails + '/activesupport/lib'
|
8
|
-
$:.unshift linked_rails + '/actionpack/lib'
|
9
|
-
else
|
10
|
-
require 'rubygems'
|
11
|
-
end
|
12
|
-
require 'action_controller'
|
13
|
-
require 'action_view'
|
2
|
+
require File.dirname(__FILE__) + '/linked_rails'
|
14
3
|
|
15
4
|
require 'test/unit'
|
16
5
|
$:.unshift lib_dir unless $:.include?(lib_dir)
|
17
6
|
require 'haml'
|
18
7
|
require 'sass'
|
8
|
+
|
9
|
+
# required because of Sass::Plugin
|
10
|
+
unless defined? RAILS_ROOT
|
11
|
+
RAILS_ROOT = '.'
|
12
|
+
MERB_ENV = RAILS_ENV = 'testing'
|
13
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: haml
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Weizenbaum
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2008-
|
13
|
+
date: 2008-10-30 00:00:00 -07:00
|
14
14
|
default_executable:
|
15
15
|
dependencies: []
|
16
16
|
|
@@ -134,6 +134,7 @@ files:
|
|
134
134
|
- test/haml/templates/_partial.haml
|
135
135
|
- test/haml/templates/nuke_outer_whitespace.haml
|
136
136
|
- test/haml/templates/_av_partial_2.haml
|
137
|
+
- test/haml/templates/partial_layout.haml
|
137
138
|
- test/haml/templates/helpful.haml
|
138
139
|
- test/haml/templates/just_stuff.haml
|
139
140
|
- test/haml/templates/silent_script.haml
|
@@ -145,6 +146,7 @@ files:
|
|
145
146
|
- test/haml/templates/partials.haml
|
146
147
|
- test/haml/templates/standard.haml
|
147
148
|
- test/haml/templates/partialize.haml
|
149
|
+
- test/haml/templates/_layout_for_partial.haml
|
148
150
|
- test/haml/templates/_av_partial_1.haml
|
149
151
|
- test/haml/templates/filters.haml
|
150
152
|
- test/haml/templates/content_for_layout.haml
|
@@ -168,10 +170,12 @@ files:
|
|
168
170
|
- test/haml/results/original_engine.xhtml
|
169
171
|
- test/haml/results/helpers.xhtml
|
170
172
|
- test/haml/results/list.xhtml
|
173
|
+
- test/haml/results/partial_layout.xhtml
|
171
174
|
- test/haml/results/tag_parsing.xhtml
|
172
175
|
- test/haml/markaby
|
173
176
|
- test/haml/markaby/standard.mab
|
174
177
|
- test/haml/engine_test.rb
|
178
|
+
- test/linked_rails.rb
|
175
179
|
- test/rails
|
176
180
|
- test/benchmark.rb
|
177
181
|
- test/test_helper.rb
|
@@ -211,7 +215,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
211
215
|
requirements: []
|
212
216
|
|
213
217
|
rubyforge_project: haml
|
214
|
-
rubygems_version: 1.
|
218
|
+
rubygems_version: 1.3.0
|
215
219
|
signing_key:
|
216
220
|
specification_version: 2
|
217
221
|
summary: An elegant, structured XHTML/XML templating engine. Comes with Sass, a similar CSS templating engine.
|