haml 1.8.2 → 2.0.0
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/FAQ +138 -0
- data/MIT-LICENSE +1 -1
- data/{README → README.rdoc} +66 -3
- data/Rakefile +111 -147
- data/VERSION +1 -1
- data/bin/css2sass +0 -0
- data/bin/haml +0 -0
- data/bin/html2haml +0 -0
- data/bin/sass +0 -0
- data/init.rb +6 -1
- data/lib/haml.rb +464 -201
- data/lib/haml/buffer.rb +117 -63
- data/lib/haml/engine.rb +63 -44
- data/lib/haml/error.rb +16 -6
- data/lib/haml/exec.rb +37 -7
- data/lib/haml/filters.rb +213 -68
- data/lib/haml/helpers.rb +95 -60
- data/lib/haml/helpers/action_view_extensions.rb +1 -1
- data/lib/haml/helpers/action_view_mods.rb +54 -6
- data/lib/haml/html.rb +6 -6
- data/lib/haml/precompiler.rb +254 -133
- data/lib/haml/template.rb +3 -6
- data/lib/haml/template/patch.rb +9 -2
- data/lib/haml/template/plugin.rb +52 -23
- data/lib/sass.rb +157 -12
- data/lib/sass/constant.rb +22 -22
- data/lib/sass/constant/color.rb +13 -13
- data/lib/sass/constant/literal.rb +7 -7
- data/lib/sass/constant/number.rb +9 -9
- data/lib/sass/constant/operation.rb +4 -4
- data/lib/sass/constant/string.rb +3 -3
- data/lib/sass/css.rb +104 -31
- data/lib/sass/engine.rb +120 -39
- data/lib/sass/error.rb +1 -1
- data/lib/sass/plugin.rb +14 -3
- data/lib/sass/plugin/merb.rb +6 -2
- data/lib/sass/tree/attr_node.rb +5 -5
- data/lib/sass/tree/directive_node.rb +2 -7
- data/lib/sass/tree/node.rb +1 -12
- data/lib/sass/tree/rule_node.rb +39 -31
- data/lib/sass/tree/value_node.rb +1 -1
- data/test/benchmark.rb +67 -80
- data/test/haml/engine_test.rb +284 -84
- data/test/haml/helper_test.rb +51 -15
- data/test/haml/results/content_for_layout.xhtml +1 -2
- data/test/haml/results/eval_suppressed.xhtml +2 -4
- data/test/haml/results/filters.xhtml +44 -15
- data/test/haml/results/helpers.xhtml +2 -3
- data/test/haml/results/just_stuff.xhtml +2 -6
- data/test/haml/results/nuke_inner_whitespace.xhtml +34 -0
- data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
- data/test/haml/results/original_engine.xhtml +3 -7
- data/test/haml/results/partials.xhtml +1 -0
- data/test/haml/results/tag_parsing.xhtml +1 -6
- data/test/haml/results/very_basic.xhtml +2 -4
- data/test/haml/results/whitespace_handling.xhtml +13 -21
- data/test/haml/template_test.rb +8 -15
- data/test/haml/templates/_partial.haml +1 -0
- data/test/haml/templates/filters.haml +48 -7
- data/test/haml/templates/just_stuff.haml +1 -2
- data/test/haml/templates/nuke_inner_whitespace.haml +26 -0
- data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
- data/test/haml/templates/tag_parsing.haml +0 -3
- data/test/haml/test_helper.rb +15 -0
- data/test/sass/engine_test.rb +80 -34
- data/test/sass/plugin_test.rb +1 -1
- data/test/sass/results/import.css +2 -2
- data/test/sass/results/mixins.css +95 -0
- data/test/sass/results/multiline.css +24 -0
- data/test/sass/templates/import.sass +4 -1
- data/test/sass/templates/importee.sass +4 -0
- data/test/sass/templates/mixins.sass +76 -0
- data/test/sass/templates/multiline.sass +20 -0
- metadata +65 -51
- data/lib/haml/util.rb +0 -18
- data/test/haml/runner.rb +0 -16
- data/test/profile.rb +0 -65
data/lib/haml/helpers.rb
CHANGED
@@ -34,25 +34,44 @@ module Haml
|
|
34
34
|
# include Haml::Helpers
|
35
35
|
# end
|
36
36
|
# context.init_haml_helpers
|
37
|
-
# context.
|
38
|
-
#
|
37
|
+
# context.haml_tag :p, "Stuff"
|
38
|
+
#
|
39
39
|
def init_haml_helpers
|
40
|
-
@
|
41
|
-
@haml_stack = [Haml::Buffer.new]
|
40
|
+
@haml_buffer = Haml::Buffer.new(@haml_buffer, Haml::Engine.new('').send(:options_for_buffer))
|
42
41
|
nil
|
43
42
|
end
|
44
43
|
|
45
44
|
# call-seq:
|
46
|
-
#
|
45
|
+
# non_haml { ... }
|
46
|
+
#
|
47
|
+
# Runs a block of code in a non-Haml context
|
48
|
+
# (i.e. #is_haml? will return false).
|
49
|
+
#
|
50
|
+
# This is mainly useful for rendering sub-templates such as partials in a non-Haml language,
|
51
|
+
# particularly where helpers may behave differently when run from Haml.
|
52
|
+
#
|
53
|
+
# Note that this is automatically applied to Rails partials.
|
54
|
+
def non_haml
|
55
|
+
was_active = @haml_buffer.active?
|
56
|
+
@haml_buffer.active = false
|
57
|
+
res = yield
|
58
|
+
@haml_buffer.active = was_active
|
59
|
+
res
|
60
|
+
end
|
61
|
+
|
62
|
+
# call-seq:
|
63
|
+
# find_and_preserve(input, tags = haml_buffer.options[:preserve])
|
47
64
|
# find_and_preserve {...}
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
|
65
|
+
#
|
66
|
+
# Uses preserve to convert any newlines inside whitespace-sensitive tags
|
67
|
+
# into the HTML entities for endlines.
|
68
|
+
# @tags@ is an array of tags to preserve.
|
69
|
+
# It defaults to the value of the <tt>:preserve</tt> option.
|
70
|
+
def find_and_preserve(input = '', tags = haml_buffer.options[:preserve], &block)
|
52
71
|
return find_and_preserve(capture_haml(&block)) if block
|
53
72
|
|
54
73
|
input = input.to_s
|
55
|
-
input.gsub(/<(
|
74
|
+
input.gsub(/<(#{tags.map(&Regexp.method(:escape)).join('|')})([^>]*)>(.*?)(<\/\1>)/im) do
|
56
75
|
"<#{$1}#{$2}>#{preserve($3)}</#{$1}>"
|
57
76
|
end
|
58
77
|
end
|
@@ -67,7 +86,7 @@ module Haml
|
|
67
86
|
def preserve(input = '', &block)
|
68
87
|
return preserve(capture_haml(&block)) if block
|
69
88
|
|
70
|
-
input.gsub(/\n/, '
').gsub(/\r/, '')
|
89
|
+
input.chomp("\n").gsub(/\n/, '
').gsub(/\r/, '')
|
71
90
|
end
|
72
91
|
|
73
92
|
alias_method :flatten, :preserve
|
@@ -107,14 +126,14 @@ module Haml
|
|
107
126
|
def list_of(array, &block) # :yields: item
|
108
127
|
to_return = array.collect do |i|
|
109
128
|
result = capture_haml(i, &block)
|
110
|
-
|
129
|
+
|
111
130
|
if result.count("\n") > 1
|
112
131
|
result.gsub!("\n", "\n ")
|
113
132
|
result = "\n #{result.strip}\n"
|
114
133
|
else
|
115
134
|
result.strip!
|
116
135
|
end
|
117
|
-
|
136
|
+
|
118
137
|
"<li>#{result}</li>"
|
119
138
|
end
|
120
139
|
to_return.join("\n")
|
@@ -156,14 +175,14 @@ module Haml
|
|
156
175
|
haml_buffer.tabulation += i
|
157
176
|
end
|
158
177
|
|
159
|
-
#
|
178
|
+
# Decrements the number of tabs the buffer automatically adds
|
160
179
|
# to the lines of the template.
|
161
180
|
#
|
162
|
-
# See tab_up.
|
181
|
+
# See also tab_up.
|
163
182
|
def tab_down(i = 1)
|
164
183
|
haml_buffer.tabulation -= i
|
165
184
|
end
|
166
|
-
|
185
|
+
|
167
186
|
# Surrounds the given block of Haml code with the given characters,
|
168
187
|
# with no whitespace in between.
|
169
188
|
# For example:
|
@@ -187,10 +206,10 @@ module Haml
|
|
187
206
|
def surround(front, back = nil, &block)
|
188
207
|
back ||= front
|
189
208
|
output = capture_haml(&block)
|
190
|
-
|
209
|
+
|
191
210
|
"#{front}#{output.chomp}#{back}\n"
|
192
211
|
end
|
193
|
-
|
212
|
+
|
194
213
|
# Prepends the given character to the beginning of the Haml block,
|
195
214
|
# with no whitespace between.
|
196
215
|
# For example:
|
@@ -205,7 +224,7 @@ module Haml
|
|
205
224
|
def precede(char, &block)
|
206
225
|
"#{char}#{capture_haml(&block).chomp}\n"
|
207
226
|
end
|
208
|
-
|
227
|
+
|
209
228
|
# Appends the given character to the end of the Haml block,
|
210
229
|
# with no whitespace between.
|
211
230
|
# For example:
|
@@ -222,7 +241,7 @@ module Haml
|
|
222
241
|
def succeed(char, &block)
|
223
242
|
"#{capture_haml(&block).chomp}#{char}\n"
|
224
243
|
end
|
225
|
-
|
244
|
+
|
226
245
|
# Captures the result of the given block of Haml code,
|
227
246
|
# gets rid of the excess indentation,
|
228
247
|
# and returns it as a string.
|
@@ -246,24 +265,24 @@ module Haml
|
|
246
265
|
|
247
266
|
#
|
248
267
|
# call-seq:
|
249
|
-
#
|
250
|
-
#
|
268
|
+
# haml_tag(name, attributes = {}) {...}
|
269
|
+
# haml_tag(name, text, attributes = {}) {...}
|
251
270
|
#
|
252
271
|
# Creates an HTML tag with the given name and optionally text and attributes.
|
253
272
|
# Can take a block that will be executed
|
254
273
|
# between when the opening and closing tags are output.
|
255
274
|
# If the block is a Haml block or outputs text using puts,
|
256
275
|
# the text will be properly indented.
|
257
|
-
#
|
276
|
+
#
|
258
277
|
# For example,
|
259
278
|
#
|
260
|
-
#
|
261
|
-
#
|
262
|
-
#
|
263
|
-
#
|
279
|
+
# haml_tag :table do
|
280
|
+
# haml_tag :tr do
|
281
|
+
# haml_tag :td, {:class => 'cell'} do
|
282
|
+
# haml_tag :strong, "strong!"
|
264
283
|
# puts "data"
|
265
284
|
# end
|
266
|
-
#
|
285
|
+
# haml_tag :td do
|
267
286
|
# puts "more_data"
|
268
287
|
# end
|
269
288
|
# end
|
@@ -286,18 +305,23 @@ module Haml
|
|
286
305
|
# </table>
|
287
306
|
#
|
288
307
|
def haml_tag(name, attributes = {}, alt_atts = {}, &block)
|
308
|
+
name = name.to_s
|
309
|
+
|
289
310
|
text = nil
|
290
311
|
if attributes.is_a? String
|
291
312
|
text = attributes
|
292
313
|
attributes = alt_atts
|
293
314
|
end
|
294
315
|
|
295
|
-
|
296
|
-
|
316
|
+
attributes = Haml::Precompiler.build_attributes(
|
317
|
+
haml_buffer.html?, haml_buffer.options[:attr_wrapper], attributes)
|
318
|
+
|
319
|
+
if text.nil? && block.nil? && haml_buffer.options[:autoclose].include?(name)
|
320
|
+
puts "<#{name}#{attributes} />"
|
297
321
|
return nil
|
298
322
|
end
|
299
323
|
|
300
|
-
puts "<#{name}#{
|
324
|
+
puts "<#{name}#{attributes}>"
|
301
325
|
unless text && text.empty?
|
302
326
|
tab_up
|
303
327
|
# Print out either the text (using push_text) or call the block and add an endline
|
@@ -312,22 +336,37 @@ module Haml
|
|
312
336
|
nil
|
313
337
|
end
|
314
338
|
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
339
|
+
# Characters that need to be escaped to HTML entities from user input
|
340
|
+
HTML_ESCAPE = { '&'=>'&', '<'=>'<', '>'=>'>', '"'=>'"', "'"=>''', }
|
341
|
+
|
342
|
+
# Returns a copy of <tt>text</tt> with ampersands, angle brackets and quotes
|
343
|
+
# escaped into HTML entities.
|
344
|
+
def html_escape(text)
|
345
|
+
text.to_s.gsub(/[\"><&]/) { |s| HTML_ESCAPE[s] }
|
346
|
+
end
|
347
|
+
|
348
|
+
# Escapes HTML entities in <tt>text</tt>, but without escaping an ampersand
|
349
|
+
# that is already part of an escaped entity.
|
350
|
+
def escape_once(text)
|
351
|
+
text.to_s.gsub(/[\"><]|&(?!([a-zA-Z]+|(#\d+));)/) { |s| HTML_ESCAPE[s] }
|
322
352
|
end
|
323
|
-
|
353
|
+
|
354
|
+
# Returns whether or not the current template is a Haml template.
|
355
|
+
#
|
356
|
+
# This function, unlike other Haml::Helpers functions,
|
357
|
+
# also works in other ActionView templates,
|
358
|
+
# where it will always return false.
|
359
|
+
def is_haml?
|
360
|
+
!@haml_buffer.nil? && @haml_buffer.active?
|
361
|
+
end
|
362
|
+
|
324
363
|
private
|
325
364
|
|
326
365
|
# Gets a reference to the current Haml::Buffer object.
|
327
366
|
def haml_buffer
|
328
|
-
@
|
367
|
+
@haml_buffer
|
329
368
|
end
|
330
|
-
|
369
|
+
|
331
370
|
# Gives a proc the same local "_hamlout" and "_erbout" variables
|
332
371
|
# that the current template has.
|
333
372
|
def haml_bind_proc(&proc)
|
@@ -335,46 +374,42 @@ END
|
|
335
374
|
_erbout = _hamlout.buffer
|
336
375
|
proc { |*args| proc.call(*args) }
|
337
376
|
end
|
338
|
-
|
377
|
+
|
339
378
|
# Performs the function of capture_haml, assuming <tt>local_buffer</tt>
|
340
379
|
# is where the output of block goes.
|
341
380
|
def capture_haml_with_buffer(local_buffer, *args, &block)
|
342
381
|
position = local_buffer.length
|
343
|
-
|
382
|
+
|
344
383
|
block.call *args
|
345
|
-
|
384
|
+
|
346
385
|
captured = local_buffer.slice!(position..-1)
|
347
|
-
|
386
|
+
|
348
387
|
min_tabs = nil
|
349
388
|
captured.each do |line|
|
350
389
|
tabs = line.index(/[^ ]/)
|
351
390
|
min_tabs ||= tabs
|
352
391
|
min_tabs = min_tabs > tabs ? tabs : min_tabs
|
353
392
|
end
|
354
|
-
|
393
|
+
|
355
394
|
result = captured.map do |line|
|
356
395
|
line[min_tabs..-1]
|
357
396
|
end
|
358
397
|
result.to_s
|
359
398
|
end
|
360
399
|
|
361
|
-
# Returns whether or not the current template is a Haml template.
|
362
|
-
#
|
363
|
-
# This function, unlike other Haml::Helpers functions,
|
364
|
-
# also works in other ActionView templates,
|
365
|
-
# where it will always return false.
|
366
|
-
def is_haml?
|
367
|
-
@haml_is_haml
|
368
|
-
end
|
369
|
-
|
370
400
|
include ActionViewExtensions if self.const_defined? "ActionViewExtensions"
|
371
401
|
end
|
372
402
|
end
|
373
403
|
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
404
|
+
class Object
|
405
|
+
# Haml overrides various ActionView helpers,
|
406
|
+
# which call an #is_haml? method
|
407
|
+
# to determine whether or not the current context object
|
408
|
+
# is a proper Haml context.
|
409
|
+
# Because ActionView helpers may be included in non-ActionView::Base classes,
|
410
|
+
# it's a good idea to define is_haml? for all objects.
|
411
|
+
def is_haml?
|
412
|
+
false
|
379
413
|
end
|
380
414
|
end
|
415
|
+
|
@@ -2,11 +2,8 @@ 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
|
-
|
6
|
-
|
7
|
-
res = render_without_haml(*args, &block)
|
8
|
-
@haml_is_haml = was_haml
|
9
|
-
res
|
5
|
+
return non_haml { render_without_haml(*args, &block) } if is_haml?
|
6
|
+
render_without_haml(*args, &block)
|
10
7
|
end
|
11
8
|
alias_method :render_without_haml, :render
|
12
9
|
alias_method :render, :render_with_haml
|
@@ -17,6 +14,28 @@ if defined?(ActionView) and not defined?(Merb::Plugins)
|
|
17
14
|
module Helpers
|
18
15
|
# :stopdoc:
|
19
16
|
module CaptureHelper
|
17
|
+
def capture_with_haml(*args, &block)
|
18
|
+
# Rails' #capture helper will just return the value of the block
|
19
|
+
# if it's not actually in the template context,
|
20
|
+
# as detected by the existance of an _erbout variable.
|
21
|
+
# We've got to do the same thing for compatibility.
|
22
|
+
block_is_haml =
|
23
|
+
begin
|
24
|
+
eval('_hamlout', block)
|
25
|
+
true
|
26
|
+
rescue
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
if block_is_haml && is_haml?
|
31
|
+
capture_haml(*args, &block)
|
32
|
+
else
|
33
|
+
capture_without_haml(*args, &block)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
alias_method :capture_without_haml, :capture
|
37
|
+
alias_method :capture, :capture_with_haml
|
38
|
+
|
20
39
|
def capture_erb_with_buffer_with_haml(*args, &block)
|
21
40
|
if is_haml?
|
22
41
|
capture_haml_with_buffer(*args, &block)
|
@@ -40,6 +59,35 @@ if defined?(ActionView) and not defined?(Merb::Plugins)
|
|
40
59
|
alias_method :concat, :concat_with_haml
|
41
60
|
end
|
42
61
|
|
62
|
+
module TagHelper
|
63
|
+
def content_tag_with_haml(name, *args, &block)
|
64
|
+
content = content_tag_without_haml(name, *args, &block)
|
65
|
+
|
66
|
+
if is_haml? && haml_buffer.options[:preserve].include?(name.to_s)
|
67
|
+
content = Haml::Helpers.preserve content
|
68
|
+
end
|
69
|
+
|
70
|
+
content
|
71
|
+
end
|
72
|
+
alias_method :content_tag_without_haml, :content_tag
|
73
|
+
alias_method :content_tag, :content_tag_with_haml
|
74
|
+
end
|
75
|
+
|
76
|
+
class InstanceTag
|
77
|
+
# Includes TagHelper
|
78
|
+
|
79
|
+
def haml_buffer
|
80
|
+
@template_object.send :haml_buffer
|
81
|
+
end
|
82
|
+
|
83
|
+
def is_haml?
|
84
|
+
@template_object.send :is_haml?
|
85
|
+
end
|
86
|
+
|
87
|
+
alias_method :content_tag_without_haml, :content_tag
|
88
|
+
alias_method :content_tag, :content_tag_with_haml
|
89
|
+
end
|
90
|
+
|
43
91
|
module FormTagHelper
|
44
92
|
def form_tag_with_haml(url_for_options = {}, options = {}, *parameters_for_url, &proc)
|
45
93
|
if is_haml?
|
@@ -66,7 +114,7 @@ if defined?(ActionView) and not defined?(Merb::Plugins)
|
|
66
114
|
module FormHelper
|
67
115
|
def form_for_with_haml(object_name, *args, &proc)
|
68
116
|
if block_given? && is_haml?
|
69
|
-
oldproc = proc
|
117
|
+
oldproc = proc
|
70
118
|
proc = haml_bind_proc do |*args|
|
71
119
|
tab_up
|
72
120
|
oldproc.call(*args)
|
data/lib/haml/html.rb
CHANGED
@@ -59,7 +59,7 @@ module Haml
|
|
59
59
|
String.new
|
60
60
|
else
|
61
61
|
lines = text.split("\n")
|
62
|
-
|
62
|
+
|
63
63
|
lines.map do |line|
|
64
64
|
line.strip!
|
65
65
|
"#{tabulate(tabs)}#{'\\' if Haml::Engine::SPECIAL_CHARACTERS.include?(line[0])}#{line}\n"
|
@@ -73,7 +73,7 @@ module Haml
|
|
73
73
|
def self.options
|
74
74
|
@@options
|
75
75
|
end
|
76
|
-
|
76
|
+
|
77
77
|
TEXT_REGEXP = /^(\s*).*$/
|
78
78
|
|
79
79
|
class ::Hpricot::Doc
|
@@ -129,14 +129,14 @@ module Haml
|
|
129
129
|
|
130
130
|
class ::Hpricot::Elem
|
131
131
|
def to_haml(tabs = 0)
|
132
|
-
output = "#{tabulate(tabs)}"
|
132
|
+
output = "#{tabulate(tabs)}"
|
133
133
|
if HTML.options[:rhtml] && name[0...5] == 'haml:'
|
134
134
|
return output + HTML.send("haml_tag_#{name[5..-1]}",
|
135
135
|
CGI.unescapeHTML(self.innerHTML))
|
136
136
|
end
|
137
137
|
|
138
138
|
output += "%#{name}" unless name == 'div' && (attributes.include?('id') || attributes.include?('class'))
|
139
|
-
|
139
|
+
|
140
140
|
if attributes
|
141
141
|
output += "##{attributes['id']}" if attributes['id']
|
142
142
|
attributes['class'].split(' ').each { |c| output += ".#{c}" } if attributes['class']
|
@@ -144,10 +144,10 @@ module Haml
|
|
144
144
|
remove_attribute('class')
|
145
145
|
output += haml_attributes if attributes.length > 0
|
146
146
|
end
|
147
|
-
|
147
|
+
|
148
148
|
output += "/" if children.length == 0
|
149
149
|
output += "\n"
|
150
|
-
|
150
|
+
|
151
151
|
self.children.each do |child|
|
152
152
|
output += child.to_haml(tabs + 1)
|
153
153
|
end
|
data/lib/haml/precompiler.rb
CHANGED
@@ -14,12 +14,15 @@ module Haml
|
|
14
14
|
# Designates an XHTML/XML comment.
|
15
15
|
COMMENT = ?/
|
16
16
|
|
17
|
-
# Designates an XHTML doctype.
|
17
|
+
# Designates an XHTML doctype or script that is never HTML-escaped.
|
18
18
|
DOCTYPE = ?!
|
19
19
|
|
20
20
|
# Designates script, the result of which is output.
|
21
21
|
SCRIPT = ?=
|
22
22
|
|
23
|
+
# Designates script that is always HTML-escaped.
|
24
|
+
SANITIZE = ?&
|
25
|
+
|
23
26
|
# Designates script, the result of which is flattened and output.
|
24
27
|
FLAT_SCRIPT = ?~
|
25
28
|
|
@@ -47,6 +50,7 @@ module Haml
|
|
47
50
|
COMMENT,
|
48
51
|
DOCTYPE,
|
49
52
|
SCRIPT,
|
53
|
+
SANITIZE,
|
50
54
|
FLAT_SCRIPT,
|
51
55
|
SILENT_SCRIPT,
|
52
56
|
ESCAPE,
|
@@ -75,15 +79,9 @@ module Haml
|
|
75
79
|
# is a member of this array.
|
76
80
|
MID_BLOCK_KEYWORDS = ['else', 'elsif', 'rescue', 'ensure', 'when']
|
77
81
|
|
78
|
-
# The Regex that matches an HTML comment command.
|
79
|
-
COMMENT_REGEX = /\/(\[[\w\s\.]*\])?(.*)/
|
80
|
-
|
81
82
|
# The Regex that matches a Doctype command.
|
82
83
|
DOCTYPE_REGEX = /(\d\.\d)?[\s]*([a-z]*)/i
|
83
84
|
|
84
|
-
# The Regex that matches an HTML tag command.
|
85
|
-
TAG_REGEX = /[%]([-:\w]+)([-\w\.\#]*)(\{.*\})?(\[.*\])?([=\/\~]?)?(.*)?/
|
86
|
-
|
87
85
|
# The Regex that matches a literal string or symbol value
|
88
86
|
LITERAL_VALUE_REGEX = /^\s*(:(\w*)|(('|")([^\\\#'"]*?)\4))\s*$/
|
89
87
|
|
@@ -93,15 +91,12 @@ module Haml
|
|
93
91
|
def precompiled_with_ambles(local_names)
|
94
92
|
preamble = <<END.gsub("\n", ";")
|
95
93
|
extend Haml::Helpers
|
96
|
-
@
|
97
|
-
@haml_stack.push(Haml::Buffer.new(#{options_for_buffer.inspect}))
|
98
|
-
@haml_is_haml = true
|
99
|
-
_hamlout = @haml_stack[-1]
|
94
|
+
_hamlout = @haml_buffer = Haml::Buffer.new(@haml_buffer, #{options_for_buffer.inspect})
|
100
95
|
_erbout = _hamlout.buffer
|
101
96
|
END
|
102
97
|
postamble = <<END.gsub("\n", ";")
|
103
|
-
@
|
104
|
-
|
98
|
+
@haml_buffer = @haml_buffer.upper
|
99
|
+
_erbout
|
105
100
|
END
|
106
101
|
preamble + locals_code(local_names) + @precompiled + postamble
|
107
102
|
end
|
@@ -122,12 +117,12 @@ END
|
|
122
117
|
@tab_change = 0
|
123
118
|
|
124
119
|
old_line = Line.new
|
125
|
-
(@template + "\n-#\n-#").split(/\n
|
120
|
+
(@template + "\n-#\n-#").split(/\r\n|\r|\n/).each_with_index do |text, index|
|
126
121
|
line = Line.new text.strip, text.lstrip.chomp, index
|
127
122
|
line.spaces, line.tabs = count_soft_tabs(text)
|
128
123
|
|
129
124
|
if line.text.empty?
|
130
|
-
process_indent(old_line)
|
125
|
+
process_indent(old_line) if flat? && !old_line.text.empty?
|
131
126
|
|
132
127
|
unless flat?
|
133
128
|
newline
|
@@ -144,6 +139,7 @@ END
|
|
144
139
|
|
145
140
|
if old_line.text.nil? || suppress_render
|
146
141
|
old_line = line
|
142
|
+
resolve_newlines
|
147
143
|
newline
|
148
144
|
next
|
149
145
|
end
|
@@ -158,15 +154,20 @@ END
|
|
158
154
|
end
|
159
155
|
|
160
156
|
if old_line.spaces != old_line.tabs * 2
|
161
|
-
raise SyntaxError.new(
|
157
|
+
raise SyntaxError.new(<<END.strip, 1 + old_line.index - @index)
|
158
|
+
#{old_line.spaces} space#{old_line.spaces == 1 ? ' was' : 's were'} used for indentation. Haml must be indented using two spaces.
|
159
|
+
END
|
162
160
|
end
|
163
161
|
|
164
162
|
unless old_line.text.empty? || @haml_comment
|
165
163
|
process_line(old_line.text, old_line.index, line.tabs > old_line.tabs && !line.text.empty?)
|
166
164
|
end
|
165
|
+
resolve_newlines
|
167
166
|
|
168
167
|
if !flat? && line.tabs - old_line.tabs > 1
|
169
|
-
raise SyntaxError.new(
|
168
|
+
raise SyntaxError.new(<<END.strip, 2 + old_line.index - @index)
|
169
|
+
#{line.spaces} spaces were used for indentation. Haml must be indented using two spaces.
|
170
|
+
END
|
170
171
|
end
|
171
172
|
old_line = line
|
172
173
|
newline
|
@@ -176,7 +177,7 @@ END
|
|
176
177
|
close until @to_close_stack.empty?
|
177
178
|
flush_merged_text
|
178
179
|
end
|
179
|
-
|
180
|
+
|
180
181
|
# Processes and deals with lowering indentation.
|
181
182
|
def process_indent(line)
|
182
183
|
return unless line.tabs <= @template_tabs && @template_tabs > 0
|
@@ -196,28 +197,35 @@ END
|
|
196
197
|
case text[0]
|
197
198
|
when DIV_CLASS, DIV_ID; render_div(text)
|
198
199
|
when ELEMENT; render_tag(text)
|
199
|
-
when COMMENT; render_comment(text)
|
200
|
+
when COMMENT; render_comment(text[1..-1].strip)
|
201
|
+
when SANITIZE
|
202
|
+
return push_script(unescape_interpolation(text[3..-1].strip), false, false, false, true) if text[1..2] == "=="
|
203
|
+
return push_script(text[2..-1].strip, false, false, false, true) if text[1] == SCRIPT
|
204
|
+
push_plain text
|
200
205
|
when SCRIPT
|
201
206
|
return push_script(unescape_interpolation(text[2..-1].strip), false) if text[1] == SCRIPT
|
207
|
+
return push_script(text[1..-1], false, false, false, true) if options[:escape_html]
|
202
208
|
push_script(text[1..-1], false)
|
203
209
|
when FLAT_SCRIPT; push_flat_script(text[1..-1])
|
204
210
|
when SILENT_SCRIPT
|
205
211
|
return start_haml_comment if text[1] == SILENT_COMMENT
|
206
212
|
|
207
213
|
push_silent(text[1..-1], true)
|
208
|
-
|
214
|
+
newline_now
|
209
215
|
if (@block_opened && !mid_block_keyword?(text)) || text[1..-1].split(' ', 2)[0] == "case"
|
210
216
|
push_and_tabulate([:script])
|
211
217
|
end
|
212
218
|
when FILTER; start_filtered(text[1..-1].downcase)
|
213
219
|
when DOCTYPE
|
214
220
|
return render_doctype(text) if text[0...3] == '!!!'
|
221
|
+
return push_script(unescape_interpolation(text[3..-1].strip), false) if text[1..2] == "=="
|
222
|
+
return push_script(text[2..-1].strip, false) if text[1] == SCRIPT
|
215
223
|
push_plain text
|
216
224
|
when ESCAPE; push_plain text[1..-1]
|
217
225
|
else push_plain text
|
218
226
|
end
|
219
227
|
end
|
220
|
-
|
228
|
+
|
221
229
|
# Returns whether or not the text is a silent script text with one
|
222
230
|
# of Ruby's mid-block keywords.
|
223
231
|
def mid_block_keyword?(text)
|
@@ -237,7 +245,7 @@ END
|
|
237
245
|
@multiline.text << text[0...-1]
|
238
246
|
return true
|
239
247
|
end
|
240
|
-
|
248
|
+
|
241
249
|
# A multiline string has just been activated, start adding the lines
|
242
250
|
if is_multiline?(text) && (MULTILINE_STARTERS.include? text[0])
|
243
251
|
@multiline = Line.new text[0...-1], nil, line.index, nil, line.tabs
|
@@ -262,73 +270,91 @@ END
|
|
262
270
|
# Evaluates <tt>text</tt> in the context of the scope object, but
|
263
271
|
# does not output the result.
|
264
272
|
def push_silent(text, can_suppress = false)
|
265
|
-
flush_merged_text
|
273
|
+
flush_merged_text
|
266
274
|
return if can_suppress && options[:suppress_eval]
|
267
275
|
@precompiled << "#{text};"
|
268
276
|
end
|
269
277
|
|
270
278
|
# Adds <tt>text</tt> to <tt>@buffer</tt> with appropriate tabulation
|
271
279
|
# without parsing it.
|
272
|
-
def push_merged_text(text, tab_change = 0,
|
273
|
-
@merged_text << "#{' ' * @output_tabs}#{text}"
|
280
|
+
def push_merged_text(text, tab_change = 0, indent = true)
|
281
|
+
@merged_text << (!indent || @dont_indent_next_line || @options[:ugly] ? text : "#{' ' * @output_tabs}#{text}")
|
282
|
+
@dont_indent_next_line = false
|
274
283
|
@tab_change += tab_change
|
275
|
-
@try_one_liner = try_one_liner
|
276
284
|
end
|
277
|
-
|
278
|
-
|
279
|
-
|
285
|
+
|
286
|
+
# Concatenate <tt>text</tt> to <tt>@buffer</tt> without tabulation.
|
287
|
+
def concat_merged_text(text)
|
288
|
+
@merged_text << text
|
280
289
|
end
|
281
|
-
|
290
|
+
|
291
|
+
def push_text(text, tab_change = 0)
|
292
|
+
push_merged_text("#{text}\n", tab_change)
|
293
|
+
end
|
294
|
+
|
282
295
|
def flush_merged_text
|
283
296
|
return if @merged_text.empty?
|
284
297
|
|
285
298
|
@precompiled << "_hamlout.push_text(#{@merged_text.dump}"
|
286
|
-
@precompiled << ", #{@
|
299
|
+
@precompiled << ", #{@dont_tab_up_next_text.inspect}" if @dont_tab_up_next_text || @tab_change != 0
|
300
|
+
@precompiled << ", #{@tab_change}" if @tab_change != 0
|
287
301
|
@precompiled << ");"
|
288
302
|
@merged_text = ''
|
303
|
+
@dont_tab_up_next_text = false
|
289
304
|
@tab_change = 0
|
290
|
-
|
291
|
-
end
|
305
|
+
end
|
292
306
|
|
293
307
|
# Renders a block of text as plain text.
|
294
308
|
# Also checks for an illegally opened block.
|
295
309
|
def push_plain(text)
|
296
|
-
raise SyntaxError.new("Illegal
|
310
|
+
raise SyntaxError.new("Illegal nesting: nesting within plain text is illegal.", 1) if @block_opened
|
297
311
|
push_text text
|
298
312
|
end
|
299
313
|
|
300
314
|
# Adds +text+ to <tt>@buffer</tt> while flattening text.
|
301
315
|
def push_flat(line)
|
302
|
-
|
303
|
-
|
304
|
-
|
316
|
+
unless @options[:ugly]
|
317
|
+
tabulation = line.spaces - @flat_spaces
|
318
|
+
tabulation = tabulation > -1 ? tabulation : 0
|
319
|
+
@filter_buffer << "#{' ' * tabulation}#{line.unstripped}\n"
|
320
|
+
else
|
321
|
+
@filter_buffer << "#{line.unstripped}\n"
|
322
|
+
end
|
305
323
|
end
|
306
324
|
|
307
325
|
# Causes <tt>text</tt> to be evaluated in the context of
|
308
326
|
# the scope object and the result to be added to <tt>@buffer</tt>.
|
309
327
|
#
|
310
|
-
# If <tt>
|
328
|
+
# If <tt>preserve_script</tt> is true, Haml::Helpers#find_and_flatten is run on
|
311
329
|
# the result before it is added to <tt>@buffer</tt>
|
312
|
-
def push_script(text,
|
330
|
+
def push_script(text, preserve_script, in_tag = false, preserve_tag = false,
|
331
|
+
escape_html = false, nuke_inner_whitespace = false)
|
332
|
+
# Prerender tabulation unless we're in a tag
|
333
|
+
push_merged_text '' unless in_tag
|
334
|
+
|
313
335
|
flush_merged_text
|
314
336
|
return if options[:suppress_eval]
|
315
337
|
|
338
|
+
raise SyntaxError.new("There's no Ruby code for = to evaluate.") if text.empty?
|
339
|
+
|
316
340
|
push_silent "haml_temp = #{text}"
|
317
|
-
|
318
|
-
|
341
|
+
newline_now
|
342
|
+
args = [preserve_script, in_tag, preserve_tag,
|
343
|
+
escape_html, nuke_inner_whitespace].map { |a| a.inspect }.join(', ')
|
344
|
+
out = "haml_temp = _hamlout.push_script(haml_temp, #{args});"
|
319
345
|
if @block_opened
|
320
346
|
push_and_tabulate([:loud, out])
|
321
347
|
else
|
322
348
|
@precompiled << out
|
323
349
|
end
|
324
350
|
end
|
325
|
-
|
351
|
+
|
326
352
|
# Causes <tt>text</tt> to be evaluated, and Haml::Helpers#find_and_flatten
|
327
353
|
# to be run on it afterwards.
|
328
354
|
def push_flat_script(text)
|
329
355
|
flush_merged_text
|
330
|
-
|
331
|
-
raise SyntaxError.new("
|
356
|
+
|
357
|
+
raise SyntaxError.new("There's no Ruby code for ~ to evaluate.") if text.empty?
|
332
358
|
push_script(text, true)
|
333
359
|
end
|
334
360
|
|
@@ -354,10 +380,14 @@ END
|
|
354
380
|
|
355
381
|
# Puts a line in <tt>@precompiled</tt> that will add the closing tag of
|
356
382
|
# the most recently opened tag.
|
357
|
-
def close_tag(
|
358
|
-
|
383
|
+
def close_tag(value)
|
384
|
+
tag, nuke_outer_whitespace, nuke_inner_whitespace = value
|
385
|
+
@output_tabs -= 1 unless nuke_inner_whitespace
|
359
386
|
@template_tabs -= 1
|
360
|
-
|
387
|
+
rstrip_buffer! if nuke_inner_whitespace
|
388
|
+
push_merged_text("</#{tag}>" + (nuke_outer_whitespace ? "" : "\n"),
|
389
|
+
nuke_inner_whitespace ? 0 : -1, !nuke_inner_whitespace)
|
390
|
+
@dont_indent_next_line = nuke_outer_whitespace
|
361
391
|
end
|
362
392
|
|
363
393
|
# Closes a Ruby block.
|
@@ -373,7 +403,7 @@ END
|
|
373
403
|
close_tag = has_conditional ? "<![endif]-->" : "-->"
|
374
404
|
push_text(close_tag, -1)
|
375
405
|
end
|
376
|
-
|
406
|
+
|
377
407
|
# Closes a loud Ruby block.
|
378
408
|
def close_loud(command)
|
379
409
|
push_silent 'end', true
|
@@ -384,14 +414,7 @@ END
|
|
384
414
|
# Closes a filtered block.
|
385
415
|
def close_filtered(filter)
|
386
416
|
@flat_spaces = -1
|
387
|
-
|
388
|
-
|
389
|
-
if filter == Haml::Filters::Preserve
|
390
|
-
push_silent("_hamlout.buffer << #{filtered.dump} << \"\\n\";")
|
391
|
-
else
|
392
|
-
push_text(filtered.rstrip.gsub("\n", "\n#{' ' * @output_tabs}"))
|
393
|
-
end
|
394
|
-
|
417
|
+
filter.internal_compile(self, @filter_buffer)
|
395
418
|
@filter_buffer = nil
|
396
419
|
@template_tabs -= 1
|
397
420
|
end
|
@@ -400,7 +423,7 @@ END
|
|
400
423
|
@haml_comment = false
|
401
424
|
@template_tabs -= 1
|
402
425
|
end
|
403
|
-
|
426
|
+
|
404
427
|
# Iterates through the classes and ids supplied through <tt>.</tt>
|
405
428
|
# and <tt>#</tt> syntax, and returns a hash with them as attributes,
|
406
429
|
# that can then be merged with another attributes hash.
|
@@ -429,8 +452,8 @@ END
|
|
429
452
|
# $5 holds the value matched by a string
|
430
453
|
$2 || $5
|
431
454
|
end
|
432
|
-
|
433
|
-
def parse_static_hash(text)
|
455
|
+
|
456
|
+
def parse_static_hash(text)
|
434
457
|
return {} unless text
|
435
458
|
|
436
459
|
attributes = {}
|
@@ -449,14 +472,23 @@ END
|
|
449
472
|
end
|
450
473
|
|
451
474
|
# This is a class method so it can be accessed from Buffer.
|
452
|
-
def self.build_attributes(attr_wrapper, attributes = {})
|
475
|
+
def self.build_attributes(is_html, attr_wrapper, attributes = {})
|
453
476
|
quote_escape = attr_wrapper == '"' ? """ : "'"
|
454
477
|
other_quote_char = attr_wrapper == '"' ? "'" : '"'
|
455
|
-
|
478
|
+
|
456
479
|
result = attributes.collect do |attr, value|
|
457
480
|
next if value.nil?
|
458
481
|
|
459
|
-
value
|
482
|
+
if value == true
|
483
|
+
next " #{attr}" if is_html
|
484
|
+
next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
|
485
|
+
elsif value == false
|
486
|
+
next
|
487
|
+
end
|
488
|
+
|
489
|
+
value = Haml::Helpers.escape_once(value.to_s)
|
490
|
+
# We want to decide whether or not to escape quotes
|
491
|
+
value.gsub!('"', '"')
|
460
492
|
this_attr_wrapper = attr_wrapper
|
461
493
|
if value.include? attr_wrapper
|
462
494
|
if value.include? other_quote_char
|
@@ -470,47 +502,70 @@ END
|
|
470
502
|
result.compact.sort.join
|
471
503
|
end
|
472
504
|
|
473
|
-
def prerender_tag(name,
|
474
|
-
|
505
|
+
def prerender_tag(name, self_close, attributes)
|
506
|
+
attributes_string = Precompiler.build_attributes(html?, @options[:attr_wrapper], attributes)
|
507
|
+
"<#{name}#{attributes_string}#{self_close && xhtml? ? ' /' : ''}>"
|
475
508
|
end
|
476
|
-
|
509
|
+
|
477
510
|
# Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value
|
478
511
|
def parse_tag(line)
|
479
|
-
raise SyntaxError.new("Invalid tag: \"#{line}\"") unless match = line.scan(
|
512
|
+
raise SyntaxError.new("Invalid tag: \"#{line}\".") unless match = line.scan(/%([-:\w]+)([-\w\.\#]*)(.*)/)[0]
|
480
513
|
tag_name, attributes, rest = match
|
481
|
-
if rest[0] == ?{
|
482
|
-
scanner = StringScanner.new(rest)
|
483
|
-
attributes_hash, rest = balance(scanner, ?{, ?})
|
484
|
-
attributes_hash = attributes_hash[1, attributes_hash.length - 2] if attributes_hash
|
485
|
-
end
|
514
|
+
attributes_hash, rest = parse_attributes(rest) if rest[0] == ?{
|
486
515
|
if rest
|
487
516
|
object_ref, rest = balance(rest, ?[, ?]) if rest[0] == ?[
|
488
|
-
|
517
|
+
attributes_hash, rest = parse_attributes(rest) if rest[0] == ?{ && attributes_hash.nil?
|
518
|
+
nuke_whitespace, action, value = rest.scan(/(<>|><|[><])?([=\/\~&!])?(.*)?/)[0]
|
519
|
+
nuke_whitespace ||= ''
|
520
|
+
nuke_outer_whitespace = nuke_whitespace.include? '>'
|
521
|
+
nuke_inner_whitespace = nuke_whitespace.include? '<'
|
489
522
|
end
|
490
523
|
value = value.to_s.strip
|
491
|
-
[tag_name, attributes, attributes_hash, object_ref,
|
524
|
+
[tag_name, attributes, attributes_hash, object_ref, nuke_outer_whitespace,
|
525
|
+
nuke_inner_whitespace, action, value]
|
526
|
+
end
|
527
|
+
|
528
|
+
def parse_attributes(line)
|
529
|
+
scanner = StringScanner.new(line)
|
530
|
+
attributes_hash, rest = balance(scanner, ?{, ?})
|
531
|
+
attributes_hash = attributes_hash[1...-1] if attributes_hash
|
532
|
+
return attributes_hash, rest
|
492
533
|
end
|
493
534
|
|
494
535
|
# Parses a line that will render as an XHTML tag, and adds the code that will
|
495
536
|
# render that tag to <tt>@precompiled</tt>.
|
496
537
|
def render_tag(line)
|
497
|
-
tag_name, attributes, attributes_hash, object_ref,
|
498
|
-
|
538
|
+
tag_name, attributes, attributes_hash, object_ref, nuke_outer_whitespace,
|
539
|
+
nuke_inner_whitespace, action, value = parse_tag(line)
|
540
|
+
|
499
541
|
raise SyntaxError.new("Illegal element: classes and ids must have values.") if attributes =~ /[\.#](\.|#|\z)/
|
500
542
|
|
543
|
+
# Get rid of whitespace outside of the tag if we need to
|
544
|
+
rstrip_buffer! if nuke_outer_whitespace
|
545
|
+
|
546
|
+
preserve_tag = options[:preserve].include?(tag_name)
|
547
|
+
nuke_inner_whitespace ||= preserve_tag
|
548
|
+
|
501
549
|
case action
|
502
|
-
when '/';
|
503
|
-
when '~'; parse =
|
550
|
+
when '/'; self_closing = xhtml?
|
551
|
+
when '~'; parse = preserve_script = true
|
504
552
|
when '='
|
505
553
|
parse = true
|
506
554
|
value = unescape_interpolation(value[1..-1].strip) if value[0] == ?=
|
555
|
+
when '&', '!'
|
556
|
+
if value[0] == ?=
|
557
|
+
parse = true
|
558
|
+
value = (value[1] == ?= ? unescape_interpolation(value[2..-1].strip) : value[1..-1].strip)
|
559
|
+
end
|
507
560
|
end
|
508
|
-
|
561
|
+
|
509
562
|
if parse && @options[:suppress_eval]
|
510
563
|
parse = false
|
511
564
|
value = ''
|
512
565
|
end
|
513
566
|
|
567
|
+
escape_html = (action == '&' || (action != '!' && @options[:escape_html]))
|
568
|
+
|
514
569
|
object_ref = "nil" if object_ref.nil? || @options[:suppress_eval]
|
515
570
|
|
516
571
|
static_attributes = parse_static_hash(attributes_hash) # Try pre-compiling a static attributes hash
|
@@ -518,41 +573,58 @@ END
|
|
518
573
|
attributes = parse_class_and_id(attributes)
|
519
574
|
Buffer.merge_attrs(attributes, static_attributes) if static_attributes
|
520
575
|
|
521
|
-
raise SyntaxError.new("Illegal
|
522
|
-
raise SyntaxError.new("Illegal
|
523
|
-
raise SyntaxError.new("
|
524
|
-
raise SyntaxError.new("
|
576
|
+
raise SyntaxError.new("Illegal nesting: nesting within a self-closing tag is illegal.", 1) if @block_opened && self_closing
|
577
|
+
raise SyntaxError.new("Illegal nesting: content can't be both given on the same line as %#{tag_name} and nested within it.", 1) if @block_opened && !value.empty?
|
578
|
+
raise SyntaxError.new("There's no Ruby code for #{action} to evaluate.") if parse && value.empty?
|
579
|
+
raise SyntaxError.new("Self-closing tags can't have content.") if self_closing && !value.empty?
|
525
580
|
|
526
|
-
|
527
|
-
|
528
|
-
if object_ref == "nil" && attributes_hash.nil? && !flattened && (parse || Buffer.one_liner?(value))
|
529
|
-
# This means that we can render the tag directly to text and not process it in the buffer
|
530
|
-
tag_closed = !value.empty? && Buffer.one_liner?(value) && !parse
|
581
|
+
self_closing ||= !!( !@block_opened && value.empty? && @options[:autoclose].include?(tag_name) )
|
531
582
|
|
532
|
-
|
533
|
-
|
534
|
-
|
583
|
+
dont_indent_next_line =
|
584
|
+
(nuke_outer_whitespace && !@block_opened) ||
|
585
|
+
(nuke_inner_whitespace && @block_opened)
|
535
586
|
|
536
|
-
|
587
|
+
# Check if we can render the tag directly to text and not process it in the buffer
|
588
|
+
if object_ref == "nil" && attributes_hash.nil? && !preserve_script
|
589
|
+
tag_closed = !@block_opened && !self_closing && !parse
|
590
|
+
|
591
|
+
open_tag = prerender_tag(tag_name, self_closing, attributes)
|
592
|
+
if tag_closed
|
593
|
+
open_tag << "#{value}</#{tag_name}>"
|
594
|
+
open_tag << "\n" unless nuke_outer_whitespace
|
595
|
+
else
|
596
|
+
open_tag << "\n" unless parse || nuke_inner_whitespace || (self_closing && nuke_outer_whitespace)
|
597
|
+
end
|
598
|
+
|
599
|
+
push_merged_text(open_tag, tag_closed || self_closing || nuke_inner_whitespace ? 0 : 1,
|
600
|
+
!nuke_outer_whitespace)
|
601
|
+
|
602
|
+
@dont_indent_next_line = dont_indent_next_line
|
537
603
|
return if tag_closed
|
538
604
|
else
|
539
605
|
flush_merged_text
|
540
606
|
content = value.empty? || parse ? 'nil' : value.dump
|
541
607
|
attributes_hash = ', ' + attributes_hash if attributes_hash
|
542
|
-
|
608
|
+
args = [tag_name, self_closing, !@block_opened, preserve_tag, escape_html,
|
609
|
+
attributes, nuke_outer_whitespace, nuke_inner_whitespace
|
610
|
+
].map { |v| v.inspect }.join(', ')
|
611
|
+
push_silent "_hamlout.open_tag(#{args}, #{object_ref}, #{content}#{attributes_hash})"
|
612
|
+
@dont_tab_up_next_text = @dont_indent_next_line = dont_indent_next_line
|
543
613
|
end
|
544
|
-
|
545
|
-
return if
|
614
|
+
|
615
|
+
return if self_closing
|
546
616
|
|
547
617
|
if value.empty?
|
548
|
-
push_and_tabulate([:element, tag_name])
|
549
|
-
@output_tabs += 1
|
618
|
+
push_and_tabulate([:element, [tag_name, nuke_outer_whitespace, nuke_inner_whitespace]])
|
619
|
+
@output_tabs += 1 unless nuke_inner_whitespace
|
550
620
|
return
|
551
621
|
end
|
552
|
-
|
622
|
+
|
553
623
|
if parse
|
554
624
|
flush_merged_text
|
555
|
-
push_script(value,
|
625
|
+
push_script(value, preserve_script, true, preserve_tag, escape_html, nuke_inner_whitespace)
|
626
|
+
@dont_tab_up_next_text = true
|
627
|
+
concat_merged_text("</#{tag_name}>" + (nuke_outer_whitespace ? "" : "\n"))
|
556
628
|
end
|
557
629
|
end
|
558
630
|
|
@@ -564,78 +636,105 @@ END
|
|
564
636
|
|
565
637
|
# Renders an XHTML comment.
|
566
638
|
def render_comment(line)
|
567
|
-
conditional,
|
568
|
-
|
639
|
+
conditional, line = balance(line, ?[, ?]) if line[0] == ?[
|
640
|
+
line.strip!
|
569
641
|
conditional << ">" if conditional
|
570
|
-
|
571
|
-
if @block_opened && !
|
572
|
-
raise SyntaxError.new('Illegal
|
642
|
+
|
643
|
+
if @block_opened && !line.empty?
|
644
|
+
raise SyntaxError.new('Illegal nesting: nesting within a tag that already has content is illegal.', 1)
|
573
645
|
end
|
574
646
|
|
575
647
|
open = "<!--#{conditional} "
|
576
|
-
|
648
|
+
|
577
649
|
# Render it statically if possible
|
578
|
-
|
579
|
-
return push_text("#{open}#{
|
650
|
+
unless line.empty?
|
651
|
+
return push_text("#{open}#{line} #{conditional ? "<![endif]-->" : "-->"}")
|
580
652
|
end
|
581
653
|
|
582
654
|
push_text(open, 1)
|
583
655
|
@output_tabs += 1
|
584
656
|
push_and_tabulate([:comment, !conditional.nil?])
|
585
|
-
unless
|
586
|
-
push_text(
|
657
|
+
unless line.empty?
|
658
|
+
push_text(line)
|
587
659
|
close
|
588
660
|
end
|
589
661
|
end
|
590
|
-
|
662
|
+
|
591
663
|
# Renders an XHTML doctype or XML shebang.
|
592
664
|
def render_doctype(line)
|
593
|
-
raise SyntaxError.new("Illegal
|
594
|
-
|
665
|
+
raise SyntaxError.new("Illegal nesting: nesting within a header command is illegal.", 1) if @block_opened
|
666
|
+
doctype = text_for_doctype(line)
|
667
|
+
push_text doctype if doctype
|
595
668
|
end
|
596
669
|
|
597
670
|
def text_for_doctype(text)
|
598
671
|
text = text[3..-1].lstrip.downcase
|
599
|
-
if text
|
672
|
+
if text.index("xml") == 0
|
673
|
+
return nil if html?
|
600
674
|
wrapper = @options[:attr_wrapper]
|
601
675
|
return "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{text.split(' ')[1] || "utf-8"}#{wrapper} ?>"
|
602
676
|
end
|
603
677
|
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
678
|
+
if html5?
|
679
|
+
'<!DOCTYPE html>'
|
680
|
+
else
|
681
|
+
version, type = text.scan(DOCTYPE_REGEX)[0]
|
608
682
|
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
683
|
+
if xhtml?
|
684
|
+
if version == "1.1"
|
685
|
+
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
|
686
|
+
else
|
687
|
+
case type
|
688
|
+
when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
|
689
|
+
when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
|
690
|
+
else '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
|
691
|
+
end
|
692
|
+
end
|
693
|
+
|
694
|
+
elsif html4?
|
695
|
+
case type
|
696
|
+
when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
|
697
|
+
when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
|
698
|
+
else '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
|
699
|
+
end
|
700
|
+
end
|
613
701
|
end
|
614
702
|
end
|
615
703
|
|
616
704
|
# Starts a filtered block.
|
617
705
|
def start_filtered(name)
|
618
|
-
raise
|
706
|
+
raise Error.new("Invalid filter name \":#{name}\".") unless name =~ /^\w+$/
|
619
707
|
|
620
|
-
unless filter =
|
708
|
+
unless filter = Filters.defined[name]
|
621
709
|
if filter == 'redcloth' || filter == 'markdown' || filter == 'textile'
|
622
|
-
raise
|
710
|
+
raise Error.new("You must have the RedCloth gem installed to use \"#{name}\" filter")
|
623
711
|
end
|
624
|
-
raise
|
712
|
+
raise Error.new("Filter \"#{name}\" is not defined.")
|
625
713
|
end
|
626
714
|
|
627
715
|
push_and_tabulate([:filtered, filter])
|
628
716
|
@flat_spaces = @template_tabs * 2
|
629
717
|
@filter_buffer = String.new
|
718
|
+
@block_opened = false
|
719
|
+
end
|
720
|
+
|
721
|
+
def contains_interpolation?(str)
|
722
|
+
str.include?('#{')
|
630
723
|
end
|
631
724
|
|
632
725
|
def unescape_interpolation(str)
|
633
726
|
scan = StringScanner.new(str.dump)
|
634
727
|
str = ''
|
635
728
|
|
636
|
-
while scan.scan(/(.*?)
|
637
|
-
|
638
|
-
str <<
|
729
|
+
while scan.scan(/(.*?)(\\+)\#\{/)
|
730
|
+
escapes = (scan[2].size - 1) / 2
|
731
|
+
str << scan.matched[0...-3 - escapes]
|
732
|
+
if escapes % 2 == 1
|
733
|
+
str << '#{'
|
734
|
+
else
|
735
|
+
# Use eval to get rid of string escapes
|
736
|
+
str << '#{' + eval('"' + balance(scan, ?{, ?}, 1)[0][0...-1] + '"') + "}"
|
737
|
+
end
|
639
738
|
end
|
640
739
|
|
641
740
|
str + scan.rest
|
@@ -654,18 +753,20 @@ END
|
|
654
753
|
|
655
754
|
raise SyntaxError.new("Unbalanced brackets.")
|
656
755
|
end
|
657
|
-
|
658
756
|
|
659
757
|
# Counts the tabulation of a line.
|
660
758
|
def count_soft_tabs(line)
|
661
759
|
spaces = line.index(/([^ ]|$)/)
|
662
760
|
if line[spaces] == ?\t
|
663
761
|
return nil if line.strip.empty?
|
664
|
-
raise SyntaxError.new(
|
762
|
+
raise SyntaxError.new(<<END.strip, 2)
|
763
|
+
A tab character was used for indentation. Haml must be indented using two spaces.
|
764
|
+
Are you sure you have soft tabs enabled in your editor?
|
765
|
+
END
|
665
766
|
end
|
666
767
|
[spaces, spaces/2]
|
667
768
|
end
|
668
|
-
|
769
|
+
|
669
770
|
# Pushes value onto <tt>@to_close_stack</tt> and increases
|
670
771
|
# <tt>@template_tabs</tt>.
|
671
772
|
def push_and_tabulate(value)
|
@@ -677,10 +778,30 @@ END
|
|
677
778
|
@flat_spaces != -1
|
678
779
|
end
|
679
780
|
|
680
|
-
def newline
|
681
|
-
|
682
|
-
|
781
|
+
def newline
|
782
|
+
@newlines += 1
|
783
|
+
end
|
784
|
+
|
785
|
+
def newline_now
|
683
786
|
@precompiled << "\n"
|
787
|
+
@newlines -= 1
|
788
|
+
end
|
789
|
+
|
790
|
+
def resolve_newlines
|
791
|
+
return unless @newlines > 0
|
792
|
+
@precompiled << "\n" * @newlines
|
793
|
+
@newlines = 0
|
794
|
+
end
|
795
|
+
|
796
|
+
# Get rid of and whitespace at the end of the buffer
|
797
|
+
# or the merged text
|
798
|
+
def rstrip_buffer!
|
799
|
+
unless @merged_text.empty?
|
800
|
+
@merged_text.rstrip!
|
801
|
+
else
|
802
|
+
push_silent("_erbout.rstrip!", false)
|
803
|
+
@dont_tab_up_next_text = true
|
804
|
+
end
|
684
805
|
end
|
685
806
|
end
|
686
807
|
end
|