haml 3.1.0.alpha.26 → 3.1.0.alpha.27

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.

@@ -29,7 +29,7 @@ module Haml
29
29
  require 'haml/helpers/xss_mods'
30
30
  Haml::Helpers.send(:include, Haml::Helpers::XssMods)
31
31
 
32
- Haml::Precompiler.module_eval do
32
+ Haml::Compiler.module_eval do
33
33
  def precompiled_method_return_value_with_haml_xss
34
34
  "::Haml::Util.html_safe(#{precompiled_method_return_value_without_haml_xss})"
35
35
  end
@@ -51,21 +51,22 @@ module Haml
51
51
  # We want to print the same deprecation warning,
52
52
  # so we have to compile in a method call to check for it.
53
53
  #
54
- # I don't like having this in the precompiler pipeline,
54
+ # I don't like having this in the compilation pipeline,
55
55
  # and I'd like to get rid of it once Rails 3.1 is well-established.
56
56
  if defined?(ActionView::OutputBuffer) &&
57
57
  Haml::Util.has?(:instance_method, ActionView::OutputBuffer, :append_if_string=)
58
- module Precompiler
59
- def push_silent_with_haml_block_deprecation(text, can_suppress = false)
60
- unless can_suppress && block_opened? && !mid_block_keyword?("- #{text}") &&
61
- text =~ ActionView::Template::Handlers::Erubis::BLOCK_EXPR
62
- return push_silent_without_haml_block_deprecation(text, can_suppress)
58
+ module Compiler
59
+ def compile_silent_script_with_haml_block_deprecation(&block)
60
+ unless block && !@node.value[:keyword] &&
61
+ @node.value[:text] =~ ActionView::Template::Handlers::Erubis::BLOCK_EXPR
62
+ return compile_silent_script_without_haml_block_deprecation(&block)
63
63
  end
64
64
 
65
- push_silent_without_haml_block_deprecation("_hamlout.append_if_string= #{text}", can_suppress)
65
+ @node.value[:text] = "_hamlout.append_if_string= #{@node.value[:text]}"
66
+ compile_silent_script_without_haml_block_deprecation(&block)
66
67
  end
67
- alias_method :push_silent_without_haml_block_deprecation, :push_silent
68
- alias_method :push_silent, :push_silent_with_haml_block_deprecation
68
+ alias_method :compile_silent_script_without_haml_block_deprecation, :compile_silent_script
69
+ alias_method :compile_silent_script, :compile_silent_script_with_haml_block_deprecation
69
70
  end
70
71
 
71
72
  class Buffer
@@ -790,6 +790,31 @@ HTML
790
790
  HAML
791
791
  end
792
792
 
793
+ def test_nested_case_assigned_to_var
794
+ assert_equal(<<HTML, render(<<HAML))
795
+ bar
796
+ HTML
797
+ - if true
798
+ - var = case 12
799
+ - when 1; "foo"
800
+ - when 12; "bar"
801
+ = var
802
+ HAML
803
+ end
804
+
805
+ def test_case_assigned_to_multiple_vars
806
+ assert_equal(<<HTML, render(<<HAML))
807
+ bar
808
+ bip
809
+ HTML
810
+ - var, vip = case 12
811
+ - when 1; ["foo", "baz"]
812
+ - when 12; ["bar", "bip"]
813
+ = var
814
+ = vip
815
+ HAML
816
+ end
817
+
793
818
  def test_if_assigned_to_var
794
819
  assert_equal(<<HTML, render(<<HAML))
795
820
  foo
@@ -1130,7 +1155,15 @@ HAML
1130
1155
  end
1131
1156
 
1132
1157
  if Haml::Util.ruby1_8?
1133
- assert_match(/^#{Regexp.escape(__FILE__)}:#{line_no}/, err.backtrace[0], "Line: #{key}")
1158
+ # Sometimes, the first backtrace entry is *only* in the message.
1159
+ # No idea why.
1160
+ bt =
1161
+ if expected_message == :compile && err.message.include?("\n")
1162
+ err.message.split("\n", 2)[1]
1163
+ else
1164
+ err.backtrace[0]
1165
+ end
1166
+ assert_match(/^#{Regexp.escape(__FILE__)}:#{line_no}/, bt, "Line: #{key}")
1134
1167
  end
1135
1168
  else
1136
1169
  assert(false, "Exception not raised for\n#{key}")
@@ -1,6 +1,7 @@
1
1
  # This is necessary for loading Sass when Haml is required in Rails 3.
2
2
  # Once the split is complete, we can remove it.
3
3
  require File.dirname(__FILE__) + '/../sass'
4
+ require 'sass/util'
4
5
 
5
6
  module Sass
6
7
  # Handles Sass version-reporting.
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: 3.1.0.alpha.26
4
+ version: 3.1.0.alpha.27
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: 2010-10-26 00:00:00 -04:00
13
+ date: 2010-10-27 00:00:00 -04:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -56,7 +56,6 @@ files:
56
56
  - lib/haml/helpers/xss_mods.rb
57
57
  - lib/haml/html.rb
58
58
  - lib/haml/html/erb.rb
59
- - lib/haml/precompiler.rb
60
59
  - lib/haml/railtie.rb
61
60
  - lib/haml/root.rb
62
61
  - lib/haml/shared.rb
@@ -66,6 +65,8 @@ files:
66
65
  - lib/haml/template/plugin.rb
67
66
  - lib/haml/util.rb
68
67
  - lib/haml/version.rb
68
+ - lib/haml/compiler.rb
69
+ - lib/haml/parser.rb
69
70
  - lib/sass.rb
70
71
  - lib/sass/plugin.rb
71
72
  - lib/sass/rails2_shim.rb
@@ -364,6 +365,7 @@ files:
364
365
  - VERSION
365
366
  - VERSION_NAME
366
367
  - EDGE_GEM_VERSION
368
+ - REVISION
367
369
  has_rdoc: false
368
370
  homepage: http://haml-lang.com/
369
371
  licenses: []
@@ -1,1113 +0,0 @@
1
- require 'strscan'
2
- require 'cgi'
3
- require 'haml/shared'
4
-
5
- module Haml
6
- # Handles the internal pre-compilation from Haml into Ruby code,
7
- # which then runs the final creation of the HTML string.
8
- module Precompiler
9
- include Haml::Util
10
-
11
- # Designates an XHTML/XML element.
12
- ELEMENT = ?%
13
-
14
- # Designates a `<div>` element with the given class.
15
- DIV_CLASS = ?.
16
-
17
- # Designates a `<div>` element with the given id.
18
- DIV_ID = ?#
19
-
20
- # Designates an XHTML/XML comment.
21
- COMMENT = ?/
22
-
23
- # Designates an XHTML doctype or script that is never HTML-escaped.
24
- DOCTYPE = ?!
25
-
26
- # Designates script, the result of which is output.
27
- SCRIPT = ?=
28
-
29
- # Designates script that is always HTML-escaped.
30
- SANITIZE = ?&
31
-
32
- # Designates script, the result of which is flattened and output.
33
- FLAT_SCRIPT = ?~
34
-
35
- # Designates script which is run but not output.
36
- SILENT_SCRIPT = ?-
37
-
38
- # When following SILENT_SCRIPT, designates a comment that is not output.
39
- SILENT_COMMENT = ?#
40
-
41
- # Designates a non-parsed line.
42
- ESCAPE = ?\\
43
-
44
- # Designates a block of filtered text.
45
- FILTER = ?:
46
-
47
- # Designates a non-parsed line. Not actually a character.
48
- PLAIN_TEXT = -1
49
-
50
- # Keeps track of the ASCII values of the characters that begin a
51
- # specially-interpreted line.
52
- SPECIAL_CHARACTERS = [
53
- ELEMENT,
54
- DIV_CLASS,
55
- DIV_ID,
56
- COMMENT,
57
- DOCTYPE,
58
- SCRIPT,
59
- SANITIZE,
60
- FLAT_SCRIPT,
61
- SILENT_SCRIPT,
62
- ESCAPE,
63
- FILTER
64
- ]
65
-
66
- # The value of the character that designates that a line is part
67
- # of a multiline string.
68
- MULTILINE_CHAR_VALUE = ?|
69
-
70
- # Regex to match keywords that appear in the middle of a Ruby block
71
- # with lowered indentation.
72
- # If a block has been started using indentation,
73
- # lowering the indentation with one of these won't end the block.
74
- # For example:
75
- #
76
- # - if foo
77
- # %p yes!
78
- # - else
79
- # %p no!
80
- #
81
- # The block is ended after `%p no!`, because `else`
82
- # is a member of this array.
83
- MID_BLOCK_KEYWORD_REGEX = /^-\s*(#{%w[else elsif rescue ensure when end].join('|')})\b/
84
-
85
- # The Regex that matches a Doctype command.
86
- DOCTYPE_REGEX = /(\d(?:\.\d)?)?[\s]*([a-z]*)/i
87
-
88
- # The Regex that matches a literal string or symbol value
89
- LITERAL_VALUE_REGEX = /:(\w*)|(["'])((?![\\#]|\2).|\\.)*\2/
90
-
91
- private
92
-
93
- # Returns the precompiled string with the preamble and postamble
94
- def precompiled_with_ambles(local_names)
95
- preamble = <<END.gsub("\n", ";")
96
- begin
97
- extend Haml::Helpers
98
- _hamlout = @haml_buffer = Haml::Buffer.new(@haml_buffer, #{options_for_buffer.inspect})
99
- _erbout = _hamlout.buffer
100
- __in_erb_template = true
101
- END
102
- postamble = <<END.gsub("\n", ";")
103
- #{precompiled_method_return_value}
104
- ensure
105
- @haml_buffer = @haml_buffer.upper
106
- end
107
- END
108
- preamble + locals_code(local_names) + precompiled + postamble
109
- end
110
-
111
- # Returns the string used as the return value of the precompiled method.
112
- # This method exists so it can be monkeypatched to return modified values.
113
- def precompiled_method_return_value
114
- "_erbout"
115
- end
116
-
117
- def locals_code(names)
118
- names = names.keys if Hash == names
119
-
120
- names.map do |name|
121
- # Can't use || because someone might explicitly pass in false with a symbol
122
- sym_local = "_haml_locals[#{inspect_obj(name.to_sym)}]"
123
- str_local = "_haml_locals[#{inspect_obj(name.to_s)}]"
124
- "#{name} = #{sym_local}.nil? ? #{str_local} : #{sym_local}"
125
- end.join(';') + ';'
126
- end
127
-
128
- # @private
129
- class Line < Struct.new(:text, :unstripped, :full, :index, :precompiler, :eod)
130
- alias_method :eod?, :eod
131
-
132
- # @private
133
- def tabs
134
- line = self
135
- @tabs ||= precompiler.instance_eval do
136
- break 0 if line.text.empty? || !(whitespace = line.full[/^\s+/])
137
-
138
- if @indentation.nil?
139
- @indentation = whitespace
140
-
141
- if @indentation.include?(?\s) && @indentation.include?(?\t)
142
- raise SyntaxError.new("Indentation can't use both tabs and spaces.", line.index)
143
- end
144
-
145
- @flat_spaces = @indentation * @template_tabs if flat?
146
- break 1
147
- end
148
-
149
- tabs = whitespace.length / @indentation.length
150
- break tabs if whitespace == @indentation * tabs
151
- break @template_tabs if flat? && whitespace =~ /^#{@indentation * @template_tabs}/
152
-
153
- raise SyntaxError.new(<<END.strip.gsub("\n", ' '), line.index)
154
- Inconsistent indentation: #{Haml::Shared.human_indentation whitespace, true} used for indentation,
155
- but the rest of the document was indented using #{Haml::Shared.human_indentation @indentation}.
156
- END
157
- end
158
- end
159
- end
160
-
161
- def precompile
162
- @haml_comment = @dont_indent_next_line = @dont_tab_up_next_text = false
163
- @indentation = nil
164
- @line = next_line
165
- resolve_newlines
166
- newline
167
-
168
- raise SyntaxError.new("Indenting at the beginning of the document is illegal.", @line.index) if @line.tabs != 0
169
-
170
- while next_line
171
- process_indent(@line) unless @line.text.empty?
172
-
173
- if flat?
174
- push_flat(@line)
175
- @line = @next_line
176
- next
177
- end
178
-
179
- process_line(@line.text, @line.index) unless @line.text.empty? || @haml_comment
180
-
181
- if !flat? && @next_line.tabs - @line.tabs > 1
182
- raise SyntaxError.new("The line was indented #{@next_line.tabs - @line.tabs} levels deeper than the previous line.", @next_line.index)
183
- end
184
-
185
- resolve_newlines unless @next_line.eod?
186
- @line = @next_line
187
- newline unless @next_line.eod?
188
- end
189
-
190
- # Close all the open tags
191
- close until @to_close_stack.empty?
192
- flush_merged_text
193
- end
194
-
195
- # Processes and deals with lowering indentation.
196
- def process_indent(line)
197
- return unless line.tabs <= @template_tabs && @template_tabs > 0
198
-
199
- to_close = @template_tabs - line.tabs
200
- to_close.times {|i| close unless to_close - 1 - i == 0 && mid_block_keyword?(line.text)}
201
- end
202
-
203
- # Processes a single line of Haml.
204
- #
205
- # This method doesn't return anything; it simply processes the line and
206
- # adds the appropriate code to `@precompiled`.
207
- def process_line(text, index)
208
- @index = index + 1
209
-
210
- case text[0]
211
- when DIV_CLASS; render_div(text)
212
- when DIV_ID
213
- return push_plain(text) if text[1] == ?{
214
- render_div(text)
215
- when ELEMENT; render_tag(text)
216
- when COMMENT; render_comment(text[1..-1].strip)
217
- when SANITIZE
218
- return push_plain(text[3..-1].strip, :escape_html => true) if text[1..2] == "=="
219
- return push_script(text[2..-1].strip, :escape_html => true) if text[1] == SCRIPT
220
- return push_flat_script(text[2..-1].strip, :escape_html => true) if text[1] == FLAT_SCRIPT
221
- return push_plain(text[1..-1].strip, :escape_html => true) if text[1] == ?\s
222
- push_plain text
223
- when SCRIPT
224
- return push_plain(text[2..-1].strip) if text[1] == SCRIPT
225
- push_script(text[1..-1])
226
- when FLAT_SCRIPT; push_flat_script(text[1..-1])
227
- when SILENT_SCRIPT
228
- return start_haml_comment if text[1] == SILENT_COMMENT
229
-
230
- raise SyntaxError.new(<<END.rstrip, index) if text[1..-1].strip == "end"
231
- You don't need to use "- end" in Haml. Un-indent to close a block:
232
- - if foo?
233
- %strong Foo!
234
- - else
235
- Not foo.
236
- %p This line is un-indented, so it isn't part of the "if" block
237
- END
238
-
239
- text = handle_ruby_multiline(text)
240
- push_silent(text[1..-1], true)
241
- newline_now
242
-
243
- # Handle stuff like - end.join("|")
244
- @to_close_stack.last << false if text =~ /^-\s*end\b/ && !block_opened?
245
-
246
- keyword = mid_block_keyword?(text)
247
- block = block_opened? && !keyword
248
-
249
- # It's important to preserve tabulation modification for keywords
250
- # that involve choosing between posible blocks of code.
251
- if %w[else elsif when].include?(keyword)
252
- # Whether a script block has already been opened immediately above this line
253
- was_opened = @to_close_stack.last && @to_close_stack.last.first == :script
254
- if was_opened
255
- @dont_indent_next_line, @dont_tab_up_next_text = @to_close_stack.last[1..2]
256
- end
257
-
258
- # when is unusual in that either it will be indented twice,
259
- # or the case won't have created its own indentation.
260
- # Also, if no block has been opened yet, we need to make sure we add an end
261
- # once we de-indent.
262
- if !was_opened || keyword == "when"
263
- push_and_tabulate([
264
- :script, @dont_indent_next_line, @dont_tab_up_next_text,
265
- !was_opened])
266
- end
267
- elsif block || text =~ /^-\s*(case|if)\b/
268
- push_and_tabulate([:script, @dont_indent_next_line, @dont_tab_up_next_text])
269
- end
270
- when FILTER; start_filtered(text[1..-1].downcase)
271
- when DOCTYPE
272
- return render_doctype(text) if text[0...3] == '!!!'
273
- return push_plain(text[3..-1].strip, :escape_html => false) if text[1..2] == "=="
274
- return push_script(text[2..-1].strip, :escape_html => false) if text[1] == SCRIPT
275
- return push_flat_script(text[2..-1].strip, :escape_html => false) if text[1] == FLAT_SCRIPT
276
- return push_plain(text[1..-1].strip, :escape_html => false) if text[1] == ?\s
277
- push_plain text
278
- when ESCAPE; push_plain text[1..-1]
279
- else push_plain text
280
- end
281
- end
282
-
283
- # If the text is a silent script text with one of Ruby's mid-block keywords,
284
- # returns the name of that keyword.
285
- # Otherwise, returns nil.
286
- def mid_block_keyword?(text)
287
- text[MID_BLOCK_KEYWORD_REGEX, 1]
288
- end
289
-
290
- # Evaluates `text` in the context of the scope object, but
291
- # does not output the result.
292
- def push_silent(text, can_suppress = false)
293
- flush_merged_text
294
- return if can_suppress && options[:suppress_eval]
295
- @precompiled << "#{text};"
296
- end
297
-
298
- # Adds `text` to `@buffer` with appropriate tabulation
299
- # without parsing it.
300
- def push_merged_text(text, tab_change = 0, indent = true)
301
- text = !indent || @dont_indent_next_line || @options[:ugly] ? text : "#{' ' * @output_tabs}#{text}"
302
- @to_merge << [:text, text, tab_change]
303
- @dont_indent_next_line = false
304
- end
305
-
306
- # Concatenate `text` to `@buffer` without tabulation.
307
- def concat_merged_text(text)
308
- @to_merge << [:text, text, 0]
309
- end
310
-
311
- def push_text(text, tab_change = 0)
312
- push_merged_text("#{text}\n", tab_change)
313
- end
314
-
315
- def flush_merged_text
316
- return if @to_merge.empty?
317
-
318
- str = ""
319
- mtabs = 0
320
- newlines = 0
321
- @to_merge.each do |type, val, tabs|
322
- case type
323
- when :text
324
- str << inspect_obj(val)[1...-1]
325
- mtabs += tabs
326
- when :script
327
- if mtabs != 0 && !@options[:ugly]
328
- val = "_hamlout.adjust_tabs(#{mtabs}); " + val
329
- end
330
- str << "\#{#{"\n" * newlines}#{val}}"
331
- mtabs = 0
332
- newlines = 0
333
- when :newlines
334
- newlines += val
335
- else
336
- raise SyntaxError.new("[HAML BUG] Undefined entry in Haml::Precompiler@to_merge.")
337
- end
338
- end
339
-
340
- unless str.empty?
341
- @precompiled <<
342
- if @options[:ugly]
343
- "_hamlout.buffer << \"#{str}\";"
344
- else
345
- "_hamlout.push_text(\"#{str}\", #{mtabs}, #{@dont_tab_up_next_text.inspect});"
346
- end
347
- end
348
- @precompiled << "\n" * newlines
349
- @to_merge = []
350
- @dont_tab_up_next_text = false
351
- end
352
-
353
- # Renders a block of text as plain text.
354
- # Also checks for an illegally opened block.
355
- def push_plain(text, options = {})
356
- if block_opened?
357
- raise SyntaxError.new("Illegal nesting: nesting within plain text is illegal.", @next_line.index)
358
- end
359
-
360
- if contains_interpolation?(text)
361
- options[:escape_html] = self.options[:escape_html] if options[:escape_html].nil?
362
- push_script(
363
- unescape_interpolation(text, :escape_html => options[:escape_html]),
364
- :escape_html => false)
365
- else
366
- push_text text
367
- end
368
- end
369
-
370
- # Adds +text+ to `@buffer` while flattening text.
371
- def push_flat(line)
372
- text = line.full.dup
373
- text = "" unless text.gsub!(/^#{@flat_spaces}/, '')
374
- @filter_buffer << "#{text}\n"
375
- end
376
-
377
- # Causes `text` to be evaluated in the context of
378
- # the scope object and the result to be added to `@buffer`.
379
- #
380
- # If `opts[:preserve_script]` is true, Haml::Helpers#find_and_flatten is run on
381
- # the result before it is added to `@buffer`
382
- def push_script(text, opts = {})
383
- raise SyntaxError.new("There's no Ruby code for = to evaluate.") if text.empty?
384
- text = handle_ruby_multiline(text)
385
- return if options[:suppress_eval]
386
- opts[:escape_html] = options[:escape_html] if opts[:escape_html].nil?
387
-
388
- args = %w[preserve_script in_tag preserve_tag escape_html nuke_inner_whitespace]
389
- args.map! {|name| opts[name.to_sym]}
390
- args << !block_opened? << @options[:ugly]
391
-
392
- no_format = @options[:ugly] &&
393
- !(opts[:preserve_script] || opts[:preserve_tag] || opts[:escape_html])
394
- output_expr = "(#{text}\n)"
395
- static_method = "_hamlout.#{static_method_name(:format_script, *args)}"
396
-
397
- # Prerender tabulation unless we're in a tag
398
- push_merged_text '' unless opts[:in_tag]
399
-
400
- unless block_opened?
401
- @to_merge << [:script, no_format ? "#{text}\n" : "#{static_method}(#{output_expr});"]
402
- concat_merged_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace]
403
- @newlines -= 1
404
- return
405
- end
406
-
407
- flush_merged_text
408
-
409
- push_silent "haml_temp = #{text}"
410
- newline_now
411
- push_and_tabulate([:loud, "_hamlout.buffer << #{no_format ? "haml_temp.to_s;" : "#{static_method}(haml_temp);"}",
412
- !(opts[:in_tag] || opts[:nuke_inner_whitespace] || @options[:ugly])])
413
- end
414
-
415
- # Causes `text` to be evaluated, and Haml::Helpers#find_and_flatten
416
- # to be run on it afterwards.
417
- def push_flat_script(text, options = {})
418
- flush_merged_text
419
-
420
- raise SyntaxError.new("There's no Ruby code for ~ to evaluate.") if text.empty?
421
- push_script(text, options.merge(:preserve_script => true))
422
- end
423
-
424
- def start_haml_comment
425
- return unless block_opened?
426
-
427
- @haml_comment = true
428
- push_and_tabulate([:haml_comment])
429
- end
430
-
431
- # Closes the most recent item in `@to_close_stack`.
432
- def close
433
- tag, *rest = @to_close_stack.pop
434
- send("close_#{tag}", *rest)
435
- end
436
-
437
- # Puts a line in `@precompiled` that will add the closing tag of
438
- # the most recently opened tag.
439
- def close_element(value)
440
- tag, nuke_outer_whitespace, nuke_inner_whitespace = value
441
- @output_tabs -= 1 unless nuke_inner_whitespace
442
- @template_tabs -= 1
443
- rstrip_buffer! if nuke_inner_whitespace
444
- push_merged_text("</#{tag}>" + (nuke_outer_whitespace ? "" : "\n"),
445
- nuke_inner_whitespace ? 0 : -1, !nuke_inner_whitespace)
446
- @dont_indent_next_line = nuke_outer_whitespace
447
- end
448
-
449
- # Closes a Ruby block.
450
- def close_script(_1, _2, push_end = true)
451
- push_silent("end", true) if push_end
452
- @template_tabs -= 1
453
- end
454
-
455
- # Closes a comment.
456
- def close_comment(has_conditional)
457
- @output_tabs -= 1
458
- @template_tabs -= 1
459
- close_tag = has_conditional ? "<![endif]-->" : "-->"
460
- push_text(close_tag, -1)
461
- end
462
-
463
- # Closes a loud Ruby block.
464
- def close_loud(command, add_newline, push_end = true)
465
- push_silent('end', true) if push_end
466
- @precompiled << command
467
- @template_tabs -= 1
468
- concat_merged_text("\n") if add_newline
469
- end
470
-
471
- # Closes a filtered block.
472
- def close_filtered(filter)
473
- filter.internal_compile(self, @filter_buffer)
474
- @flat = false
475
- @flat_spaces = nil
476
- @filter_buffer = nil
477
- @template_tabs -= 1
478
- end
479
-
480
- def close_haml_comment
481
- @haml_comment = false
482
- @template_tabs -= 1
483
- end
484
-
485
- def close_nil(*args)
486
- @template_tabs -= 1
487
- end
488
-
489
- # This is a class method so it can be accessed from {Haml::Helpers}.
490
- #
491
- # Iterates through the classes and ids supplied through `.`
492
- # and `#` syntax, and returns a hash with them as attributes,
493
- # that can then be merged with another attributes hash.
494
- def self.parse_class_and_id(list)
495
- attributes = {}
496
- list.scan(/([#.])([-:_a-zA-Z0-9]+)/) do |type, property|
497
- case type
498
- when '.'
499
- if attributes['class']
500
- attributes['class'] += " "
501
- else
502
- attributes['class'] = ""
503
- end
504
- attributes['class'] += property
505
- when '#'; attributes['id'] = property
506
- end
507
- end
508
- attributes
509
- end
510
-
511
- def parse_static_hash(text)
512
- attributes = {}
513
- scanner = StringScanner.new(text)
514
- scanner.scan(/\s+/)
515
- until scanner.eos?
516
- return unless key = scanner.scan(LITERAL_VALUE_REGEX)
517
- return unless scanner.scan(/\s*=>\s*/)
518
- return unless value = scanner.scan(LITERAL_VALUE_REGEX)
519
- return unless scanner.scan(/\s*(?:,|$)\s*/)
520
- attributes[eval(key).to_s] = eval(value).to_s
521
- end
522
- text.count("\n").times { newline }
523
- attributes
524
- end
525
-
526
- # This is a class method so it can be accessed from Buffer.
527
- def self.build_attributes(is_html, attr_wrapper, escape_attrs, attributes = {})
528
- quote_escape = attr_wrapper == '"' ? "&quot;" : "&apos;"
529
- other_quote_char = attr_wrapper == '"' ? "'" : '"'
530
-
531
- if attributes['data'].is_a?(Hash)
532
- attributes = attributes.dup
533
- attributes =
534
- Haml::Util.map_keys(attributes.delete('data')) {|name| "data-#{name}"}.merge(attributes)
535
- end
536
-
537
- result = attributes.collect do |attr, value|
538
- next if value.nil?
539
-
540
- value = filter_and_join(value, ' ') if attr == 'class'
541
- value = filter_and_join(value, '_') if attr == 'id'
542
-
543
- if value == true
544
- next " #{attr}" if is_html
545
- next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
546
- elsif value == false
547
- next
548
- end
549
-
550
- escaped =
551
- if escape_attrs == :once
552
- Haml::Helpers.escape_once(value.to_s)
553
- elsif escape_attrs
554
- CGI.escapeHTML(value.to_s)
555
- else
556
- value.to_s
557
- end
558
- value = Haml::Helpers.preserve(escaped)
559
- if escape_attrs
560
- # We want to decide whether or not to escape quotes
561
- value.gsub!('&quot;', '"')
562
- this_attr_wrapper = attr_wrapper
563
- if value.include? attr_wrapper
564
- if value.include? other_quote_char
565
- value = value.gsub(attr_wrapper, quote_escape)
566
- else
567
- this_attr_wrapper = other_quote_char
568
- end
569
- end
570
- else
571
- this_attr_wrapper = attr_wrapper
572
- end
573
- " #{attr}=#{this_attr_wrapper}#{value}#{this_attr_wrapper}"
574
- end
575
- result.compact.sort.join
576
- end
577
-
578
- def self.filter_and_join(value, separator)
579
- return "" if value == ""
580
- value = [value] unless value.is_a?(Array)
581
- value = value.flatten.collect {|item| item ? item.to_s : nil}.compact.join(separator)
582
- return !value.empty? && value
583
- end
584
-
585
- def prerender_tag(name, self_close, attributes)
586
- attributes_string = Precompiler.build_attributes(
587
- html?, @options[:attr_wrapper], @options[:escape_attrs], attributes)
588
- "<#{name}#{attributes_string}#{self_close && xhtml? ? ' /' : ''}>"
589
- end
590
-
591
- # Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value
592
- def parse_tag(line)
593
- raise SyntaxError.new("Invalid tag: \"#{line}\".") unless match = line.scan(/%([-:\w]+)([-:\w\.\#]*)(.*)/)[0]
594
- tag_name, attributes, rest = match
595
- new_attributes_hash = old_attributes_hash = last_line = object_ref = nil
596
- attributes_hashes = []
597
- while rest
598
- case rest[0]
599
- when ?{
600
- break if old_attributes_hash
601
- old_attributes_hash, rest, last_line = parse_old_attributes(rest)
602
- attributes_hashes << [:old, old_attributes_hash]
603
- when ?(
604
- break if new_attributes_hash
605
- new_attributes_hash, rest, last_line = parse_new_attributes(rest)
606
- attributes_hashes << [:new, new_attributes_hash]
607
- when ?[
608
- break if object_ref
609
- object_ref, rest = balance(rest, ?[, ?])
610
- else; break
611
- end
612
- end
613
-
614
- if rest
615
- nuke_whitespace, action, value = rest.scan(/(<>|><|[><])?([=\/\~&!])?(.*)?/)[0]
616
- nuke_whitespace ||= ''
617
- nuke_outer_whitespace = nuke_whitespace.include? '>'
618
- nuke_inner_whitespace = nuke_whitespace.include? '<'
619
- end
620
-
621
- value = value.to_s.strip
622
- [tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
623
- nuke_inner_whitespace, action, value, last_line || @index]
624
- end
625
-
626
- def parse_old_attributes(line)
627
- line = line.dup
628
- last_line = @index
629
-
630
- begin
631
- attributes_hash, rest = balance(line, ?{, ?})
632
- rescue SyntaxError => e
633
- if line.strip[-1] == ?, && e.message == "Unbalanced brackets."
634
- line << "\n" << @next_line.text
635
- last_line += 1
636
- next_line
637
- retry
638
- end
639
-
640
- raise e
641
- end
642
-
643
- attributes_hash = attributes_hash[1...-1] if attributes_hash
644
- return attributes_hash, rest, last_line
645
- end
646
-
647
- def parse_new_attributes(line)
648
- line = line.dup
649
- scanner = StringScanner.new(line)
650
- last_line = @index
651
- attributes = {}
652
-
653
- scanner.scan(/\(\s*/)
654
- loop do
655
- name, value = parse_new_attribute(scanner)
656
- break if name.nil?
657
-
658
- if name == false
659
- text = (Haml::Shared.balance(line, ?(, ?)) || [line]).first
660
- raise Haml::SyntaxError.new("Invalid attribute list: #{text.inspect}.", last_line - 1)
661
- end
662
- attributes[name] = value
663
- scanner.scan(/\s*/)
664
-
665
- if scanner.eos?
666
- line << " " << @next_line.text
667
- last_line += 1
668
- next_line
669
- scanner.scan(/\s*/)
670
- end
671
- end
672
-
673
- static_attributes = {}
674
- dynamic_attributes = "{"
675
- attributes.each do |name, (type, val)|
676
- if type == :static
677
- static_attributes[name] = val
678
- else
679
- dynamic_attributes << inspect_obj(name) << " => " << val << ","
680
- end
681
- end
682
- dynamic_attributes << "}"
683
- dynamic_attributes = nil if dynamic_attributes == "{}"
684
-
685
- return [static_attributes, dynamic_attributes], scanner.rest, last_line
686
- end
687
-
688
- def parse_new_attribute(scanner)
689
- unless name = scanner.scan(/[-:\w]+/)
690
- return if scanner.scan(/\)/)
691
- return false
692
- end
693
-
694
- scanner.scan(/\s*/)
695
- return name, [:static, true] unless scanner.scan(/=/) #/end
696
-
697
- scanner.scan(/\s*/)
698
- unless quote = scanner.scan(/["']/)
699
- return false unless var = scanner.scan(/(@@?|\$)?\w+/)
700
- return name, [:dynamic, var]
701
- end
702
-
703
- re = /((?:\\.|\#(?!\{)|[^#{quote}\\#])*)(#{quote}|#\{)/
704
- content = []
705
- loop do
706
- return false unless scanner.scan(re)
707
- content << [:str, scanner[1].gsub(/\\(.)/, '\1')]
708
- break if scanner[2] == quote
709
- content << [:ruby, balance(scanner, ?{, ?}, 1).first[0...-1]]
710
- end
711
-
712
- return name, [:static, content.first[1]] if content.size == 1
713
- return name, [:dynamic,
714
- '"' + content.map {|(t, v)| t == :str ? inspect_obj(v)[1...-1] : "\#{#{v}}"}.join + '"']
715
- end
716
-
717
- # Parses a line that will render as an XHTML tag, and adds the code that will
718
- # render that tag to `@precompiled`.
719
- def render_tag(line)
720
- tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
721
- nuke_inner_whitespace, action, value, last_line = parse_tag(line)
722
-
723
- raise SyntaxError.new("Illegal element: classes and ids must have values.") if attributes =~ /[\.#](\.|#|\z)/
724
-
725
- # Get rid of whitespace outside of the tag if we need to
726
- rstrip_buffer! if nuke_outer_whitespace
727
-
728
- preserve_tag = options[:preserve].include?(tag_name)
729
- nuke_inner_whitespace ||= preserve_tag
730
- preserve_tag &&= !options[:ugly]
731
-
732
- escape_html = (action == '&' || (action != '!' && @options[:escape_html]))
733
-
734
- case action
735
- when '/'; self_closing = true
736
- when '~'; parse = preserve_script = true
737
- when '='
738
- parse = true
739
- if value[0] == ?=
740
- value = unescape_interpolation(value[1..-1].strip, :escape_html => escape_html)
741
- escape_html = false
742
- end
743
- when '&', '!'
744
- if value[0] == ?= || value[0] == ?~
745
- parse = true
746
- preserve_script = (value[0] == ?~)
747
- if value[1] == ?=
748
- value = unescape_interpolation(value[2..-1].strip, :escape_html => escape_html)
749
- escape_html = false
750
- else
751
- value = value[1..-1].strip
752
- end
753
- elsif contains_interpolation?(value)
754
- value = unescape_interpolation(value, :escape_html => escape_html)
755
- parse = true
756
- escape_html = false
757
- end
758
- else
759
- if contains_interpolation?(value)
760
- value = unescape_interpolation(value, :escape_html => escape_html)
761
- parse = true
762
- escape_html = false
763
- end
764
- end
765
-
766
- if parse && @options[:suppress_eval]
767
- parse = false
768
- value = ''
769
- end
770
-
771
- object_ref = "nil" if object_ref.nil? || @options[:suppress_eval]
772
-
773
- attributes = Precompiler.parse_class_and_id(attributes)
774
- attributes_hashes.map! do |syntax, attributes_hash|
775
- if syntax == :old
776
- static_attributes = parse_static_hash(attributes_hash)
777
- attributes_hash = nil if static_attributes || @options[:suppress_eval]
778
- else
779
- static_attributes, attributes_hash = attributes_hash
780
- end
781
- Buffer.merge_attrs(attributes, static_attributes) if static_attributes
782
- attributes_hash
783
- end.compact!
784
-
785
- raise SyntaxError.new("Illegal nesting: nesting within a self-closing tag is illegal.", @next_line.index) if block_opened? && self_closing
786
- raise SyntaxError.new("There's no Ruby code for #{action} to evaluate.", last_line - 1) if parse && value.empty?
787
- raise SyntaxError.new("Self-closing tags can't have content.", last_line - 1) if self_closing && !value.empty?
788
-
789
- if block_opened? && !value.empty? && !is_ruby_multiline?(value)
790
- raise SyntaxError.new("Illegal nesting: content can't be both given on the same line as %#{tag_name} and nested within it.", @next_line.index)
791
- end
792
-
793
- self_closing ||= !!(!block_opened? && value.empty? && @options[:autoclose].any? {|t| t === tag_name})
794
- value = nil if value.empty? && (block_opened? || self_closing)
795
-
796
- dont_indent_next_line =
797
- (nuke_outer_whitespace && !block_opened?) ||
798
- (nuke_inner_whitespace && block_opened?)
799
-
800
- # Check if we can render the tag directly to text and not process it in the buffer
801
- if object_ref == "nil" && attributes_hashes.empty? && !preserve_script
802
- tag_closed = !block_opened? && !self_closing && !parse
803
-
804
- open_tag = prerender_tag(tag_name, self_closing, attributes)
805
- if tag_closed
806
- open_tag << "#{value}</#{tag_name}>"
807
- open_tag << "\n" unless nuke_outer_whitespace
808
- else
809
- open_tag << "\n" unless parse || nuke_inner_whitespace || (self_closing && nuke_outer_whitespace)
810
- end
811
-
812
- push_merged_text(open_tag, tag_closed || self_closing || nuke_inner_whitespace ? 0 : 1,
813
- !nuke_outer_whitespace)
814
-
815
- @dont_indent_next_line = dont_indent_next_line
816
- return if tag_closed
817
- else
818
- flush_merged_text
819
- content = parse ? 'nil' : inspect_obj(value)
820
- if attributes_hashes.empty?
821
- attributes_hashes = ''
822
- elsif attributes_hashes.size == 1
823
- attributes_hashes = ", #{attributes_hashes.first}"
824
- else
825
- attributes_hashes = ", (#{attributes_hashes.join(").merge(")})"
826
- end
827
-
828
- args = [tag_name, self_closing, !block_opened?, preserve_tag, escape_html,
829
- attributes, nuke_outer_whitespace, nuke_inner_whitespace
830
- ].map {|v| inspect_obj(v)}.join(', ')
831
- push_silent "_hamlout.open_tag(#{args}, #{object_ref}, #{content}#{attributes_hashes})"
832
- @dont_tab_up_next_text = @dont_indent_next_line = dont_indent_next_line
833
- end
834
-
835
- return if self_closing
836
-
837
- if value.nil?
838
- push_and_tabulate([:element, [tag_name, nuke_outer_whitespace, nuke_inner_whitespace]])
839
- @output_tabs += 1 unless nuke_inner_whitespace
840
- return
841
- end
842
-
843
- if parse
844
- push_script(value, :preserve_script => preserve_script, :in_tag => true,
845
- :preserve_tag => preserve_tag, :escape_html => escape_html,
846
- :nuke_inner_whitespace => nuke_inner_whitespace)
847
- concat_merged_text("</#{tag_name}>" + (nuke_outer_whitespace ? "" : "\n"))
848
- end
849
- end
850
-
851
- # Renders a line that creates an XHTML tag and has an implicit div because of
852
- # `.` or `#`.
853
- def render_div(line)
854
- render_tag('%div' + line)
855
- end
856
-
857
- # Renders an XHTML comment.
858
- def render_comment(line)
859
- conditional, line = balance(line, ?[, ?]) if line[0] == ?[
860
- line.strip!
861
- conditional << ">" if conditional
862
-
863
- if block_opened? && !line.empty?
864
- raise SyntaxError.new('Illegal nesting: nesting within a tag that already has content is illegal.', @next_line.index)
865
- end
866
-
867
- open = "<!--#{conditional}"
868
-
869
- # Render it statically if possible
870
- unless line.empty?
871
- return push_text("#{open} #{line} #{conditional ? "<![endif]-->" : "-->"}")
872
- end
873
-
874
- push_text(open, 1)
875
- @output_tabs += 1
876
- push_and_tabulate([:comment, !conditional.nil?])
877
- unless line.empty?
878
- push_text(line)
879
- close
880
- end
881
- end
882
-
883
- # Renders an XHTML doctype or XML shebang.
884
- def render_doctype(line)
885
- raise SyntaxError.new("Illegal nesting: nesting within a header command is illegal.", @next_line.index) if block_opened?
886
- doctype = text_for_doctype(line)
887
- push_text doctype if doctype
888
- end
889
-
890
- def text_for_doctype(text)
891
- text = text[3..-1].lstrip.downcase
892
- if text.index("xml") == 0
893
- return nil if html?
894
- wrapper = @options[:attr_wrapper]
895
- return "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{text.split(' ')[1] || "utf-8"}#{wrapper} ?>"
896
- end
897
-
898
- if html5?
899
- '<!DOCTYPE html>'
900
- else
901
- version, type = text.scan(DOCTYPE_REGEX)[0]
902
-
903
- if xhtml?
904
- if version == "1.1"
905
- '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
906
- elsif version == "5"
907
- '<!DOCTYPE html>'
908
- else
909
- case type
910
- when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
911
- when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
912
- when "mobile"; '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
913
- when "rdfa"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">'
914
- when "basic"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
915
- else '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
916
- end
917
- end
918
-
919
- elsif html4?
920
- case type
921
- when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
922
- when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
923
- else '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
924
- end
925
- end
926
- end
927
- end
928
-
929
- # Starts a filtered block.
930
- def start_filtered(name)
931
- raise Error.new("Invalid filter name \":#{name}\".") unless name =~ /^\w+$/
932
- raise Error.new("Filter \"#{name}\" is not defined.") unless filter = Filters.defined[name]
933
-
934
- push_and_tabulate([:filtered, filter])
935
- @flat = true
936
- @filter_buffer = String.new
937
-
938
- # If we don't know the indentation by now, it'll be set in Line#tabs
939
- @flat_spaces = @indentation * @template_tabs if @indentation
940
- end
941
-
942
- def raw_next_line
943
- text = @template.shift
944
- return unless text
945
-
946
- index = @template_index
947
- @template_index += 1
948
-
949
- return text, index
950
- end
951
-
952
- def next_line
953
- text, index = raw_next_line
954
- return unless text
955
-
956
- # :eod is a special end-of-document marker
957
- line =
958
- if text == :eod
959
- Line.new '-#', '-#', '-#', index, self, true
960
- else
961
- Line.new text.strip, text.lstrip.chomp, text, index, self, false
962
- end
963
-
964
- # `flat?' here is a little outdated,
965
- # so we have to manually check if either the previous or current line
966
- # closes the flat block,
967
- # as well as whether a new block is opened
968
- @line.tabs if @line
969
- unless (flat? && !closes_flat?(line) && !closes_flat?(@line)) ||
970
- (@line && @line.text[0] == ?: && line.full =~ %r[^#{@line.full[/^\s+/]}\s])
971
- if line.text.empty?
972
- newline
973
- return next_line
974
- end
975
-
976
- handle_multiline(line)
977
- end
978
-
979
- @next_line = line
980
- end
981
-
982
- def closes_flat?(line)
983
- line && !line.text.empty? && line.full !~ /^#{@flat_spaces}/
984
- end
985
-
986
- def un_next_line(line)
987
- @template.unshift line
988
- @template_index -= 1
989
- end
990
-
991
- def handle_multiline(line)
992
- return unless is_multiline?(line.text)
993
- line.text.slice!(-1)
994
- while new_line = raw_next_line.first
995
- break if new_line == :eod
996
- newline and next if new_line.strip.empty?
997
- break unless is_multiline?(new_line.strip)
998
- line.text << new_line.strip[0...-1]
999
- newline
1000
- end
1001
- un_next_line new_line
1002
- resolve_newlines
1003
- end
1004
-
1005
- # Checks whether or not +line+ is in a multiline sequence.
1006
- def is_multiline?(text)
1007
- text && text.length > 1 && text[-1] == MULTILINE_CHAR_VALUE && text[-2] == ?\s
1008
- end
1009
-
1010
- def handle_ruby_multiline(text)
1011
- text = text.rstrip
1012
- return text unless is_ruby_multiline?(text)
1013
- un_next_line @next_line.full
1014
- begin
1015
- new_line = raw_next_line.first
1016
- break if new_line == :eod
1017
- newline and next if new_line.strip.empty?
1018
- text << " " << new_line.strip
1019
- newline
1020
- end while is_ruby_multiline?(new_line.strip)
1021
- next_line
1022
- resolve_newlines
1023
- text
1024
- end
1025
-
1026
- def is_ruby_multiline?(text)
1027
- text && text.length > 1 && text[-1] == ?, && text[-2] != ?? && text[-3..-2] != "?\\"
1028
- end
1029
-
1030
- def contains_interpolation?(str)
1031
- str.include?('#{')
1032
- end
1033
-
1034
- def unescape_interpolation(str, opts = {})
1035
- res = ''
1036
- rest = Haml::Shared.handle_interpolation inspect_obj(str) do |scan|
1037
- escapes = (scan[2].size - 1) / 2
1038
- res << scan.matched[0...-3 - escapes]
1039
- if escapes % 2 == 1
1040
- res << '#{'
1041
- else
1042
- content = eval('"' + balance(scan, ?{, ?}, 1)[0][0...-1] + '"')
1043
- content = "Haml::Helpers.html_escape((#{content}))" if opts[:escape_html]
1044
- res << '#{' + content + "}"# Use eval to get rid of string escapes
1045
- end
1046
- end
1047
- res + rest
1048
- end
1049
-
1050
- def balance(*args)
1051
- res = Haml::Shared.balance(*args)
1052
- return res if res
1053
- raise SyntaxError.new("Unbalanced brackets.")
1054
- end
1055
-
1056
- def block_opened?
1057
- !flat? && @next_line.tabs > @line.tabs
1058
- end
1059
-
1060
- # Pushes value onto `@to_close_stack` and increases
1061
- # `@template_tabs`.
1062
- def push_and_tabulate(value)
1063
- @to_close_stack.push(value)
1064
- @template_tabs += 1
1065
- end
1066
-
1067
- def flat?
1068
- @flat
1069
- end
1070
-
1071
- def newline
1072
- @newlines += 1
1073
- end
1074
-
1075
- def newline_now
1076
- @precompiled << "\n"
1077
- @newlines -= 1
1078
- end
1079
-
1080
- def resolve_newlines
1081
- return unless @newlines > 0
1082
- @to_merge << [:newlines, @newlines]
1083
- @newlines = 0
1084
- end
1085
-
1086
- # Get rid of and whitespace at the end of the buffer
1087
- # or the merged text
1088
- def rstrip_buffer!(index = -1)
1089
- last = @to_merge[index]
1090
- if last.nil?
1091
- push_silent("_hamlout.rstrip!", false)
1092
- @dont_tab_up_next_text = true
1093
- return
1094
- end
1095
-
1096
- case last.first
1097
- when :text
1098
- last[1].rstrip!
1099
- if last[1].empty?
1100
- @to_merge.slice! index
1101
- rstrip_buffer! index
1102
- end
1103
- when :script
1104
- last[1].gsub!(/\(haml_temp, (.*?)\);$/, '(haml_temp.rstrip, \1);')
1105
- rstrip_buffer! index - 1
1106
- when :newlines
1107
- rstrip_buffer! index - 1
1108
- else
1109
- raise SyntaxError.new("[HAML BUG] Undefined entry in Haml::Precompiler@to_merge.")
1110
- end
1111
- end
1112
- end
1113
- end