Module Haml::Precompiler
In: lib/haml/precompiler.rb
Engine Error SyntaxError Buffer StandardError ActionViewExtensions Generic CSS2Sass HTML2Haml HamlSass Haml Sass Template HTML lib/haml/template.rb lib/haml/error.rb lib/haml/engine.rb lib/haml/html.rb lib/haml/buffer.rb Version Util Precompiler ActionViewExtensions Helpers Base Filters lib/haml/exec.rb Exec Haml dot/m_15_0.png

Methods

Constants

ELEMENT = ?%   Designates an XHTML/XML element.
DIV_CLASS = ?.   Designates a <div> element with the given class.
DIV_ID = ?#   Designates a <div> element with the given id.
COMMENT = ?/   Designates an XHTML/XML comment.
DOCTYPE = ?!   Designates an XHTML doctype or script that is never HTML-escaped.
SCRIPT = ?=   Designates script, the result of which is output.
SANITIZE = ?&   Designates script that is always HTML-escaped.
FLAT_SCRIPT = ?~   Designates script, the result of which is flattened and output.
SILENT_SCRIPT = ?-   Designates script which is run but not output.
SILENT_COMMENT = ?#   When following SILENT_SCRIPT, designates a comment that is not output.
ESCAPE = ?\\   Designates a non-parsed line.
FILTER = ?:   Designates a block of filtered text.
PLAIN_TEXT = -1   Designates a non-parsed line. Not actually a character.
SPECIAL_CHARACTERS = [ ELEMENT, DIV_CLASS, DIV_ID, COMMENT, DOCTYPE, SCRIPT, SANITIZE, FLAT_SCRIPT, SILENT_SCRIPT, ESCAPE, FILTER   Keeps track of the ASCII values of the characters that begin a specially-interpreted line.
MULTILINE_CHAR_VALUE = ?|   The value of the character that designates that a line is part of a multiline string.
MULTILINE_STARTERS = SPECIAL_CHARACTERS - [?/]   Characters that designate that a multiline string may be about to begin.
MID_BLOCK_KEYWORDS = ['else', 'elsif', 'rescue', 'ensure', 'when']   Keywords that appear in the middle of a Ruby block with lowered indentation. If a block has been started using indentation, lowering the indentation with one of these won‘t end the block. For example:
  - if foo
    %p yes!
  - else
    %p no!

The block is ended after %p no!, because else is a member of this array.

DOCTYPE_REGEX = /(\d\.\d)?[\s]*([a-z]*)/i   The Regex that matches a Doctype command.
LITERAL_VALUE_REGEX = /^\s*(:(\w*)|(('|")([^\\\#'"]*?)\4))\s*$/   The Regex that matches a literal string or symbol value
Line = Struct.new(:text, :unstripped, :index, :spaces, :tabs)

Private Class methods

This is a class method so it can be accessed from Buffer.

[Source]

     # File lib/haml/precompiler.rb, line 487
487:     def self.build_attributes(is_html, attr_wrapper, attributes = {})
488:       quote_escape = attr_wrapper == '"' ? "&quot;" : "&apos;"
489:       other_quote_char = attr_wrapper == '"' ? "'" : '"'
490: 
491:       result = attributes.collect do |attr, value|
492:         next if value.nil?
493: 
494:         if value == true
495:           next " #{attr}" if is_html
496:           next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
497:         elsif value == false
498:           next
499:         end
500: 
501:         value = Haml::Helpers.preserve(Haml::Helpers.escape_once(value.to_s))
502:         # We want to decide whether or not to escape quotes
503:         value.gsub!('&quot;', '"')
504:         this_attr_wrapper = attr_wrapper
505:         if value.include? attr_wrapper
506:           if value.include? other_quote_char
507:             value = value.gsub(attr_wrapper, quote_escape)
508:           else
509:             this_attr_wrapper = other_quote_char
510:           end
511:         end
512:         " #{attr}=#{this_attr_wrapper}#{value}#{this_attr_wrapper}"
513:       end
514:       result.compact.sort.join
515:     end

Private Instance methods

[Source]

     # File lib/haml/precompiler.rb, line 752
752:     def balance(scanner, start, finish, count = 0)
753:       str = ''
754:       scanner = StringScanner.new(scanner) unless scanner.is_a? StringScanner
755:       regexp = Regexp.new("(.*?)[\\#{start.chr}\\#{finish.chr}]")
756:       while scanner.scan(regexp)
757:         str << scanner.matched
758:         count += 1 if scanner.matched[-1] == start
759:         count -= 1 if scanner.matched[-1] == finish
760:         return [str.strip, scanner.rest] if count == 0
761:       end
762: 
763:       raise SyntaxError.new("Unbalanced brackets.")
764:     end

Closes the most recent item in @to_close_stack.

[Source]

     # File lib/haml/precompiler.rb, line 376
376:     def close
377:       tag, value = @to_close_stack.pop
378:       case tag
379:       when :script; close_block
380:       when :comment; close_comment value
381:       when :element; close_tag value
382:       when :loud; close_loud value
383:       when :filtered; close_filtered value
384:       when :haml_comment; close_haml_comment
385:       when nil; close_nil
386:       end
387:     end

Closes a Ruby block.

[Source]

     # File lib/haml/precompiler.rb, line 402
402:     def close_block
403:       push_silent "end", true
404:       @template_tabs -= 1
405:     end

Closes a comment.

[Source]

     # File lib/haml/precompiler.rb, line 408
408:     def close_comment(has_conditional)
409:       @output_tabs -= 1
410:       @template_tabs -= 1
411:       close_tag = has_conditional ? "<![endif]-->" : "-->"
412:       push_text(close_tag, -1)
413:     end

Closes a filtered block.

[Source]

     # File lib/haml/precompiler.rb, line 423
423:     def close_filtered(filter)
424:       @flat_spaces = -1
425:       filter.internal_compile(self, @filter_buffer)
426:       @filter_buffer = nil
427:       @template_tabs -= 1
428:     end

[Source]

     # File lib/haml/precompiler.rb, line 430
430:     def close_haml_comment
431:       @haml_comment = false
432:       @template_tabs -= 1
433:     end

Closes a loud Ruby block.

[Source]

     # File lib/haml/precompiler.rb, line 416
416:     def close_loud(command)
417:       push_silent 'end', true
418:       @precompiled << command
419:       @template_tabs -= 1
420:     end

[Source]

     # File lib/haml/precompiler.rb, line 435
435:     def close_nil
436:       @template_tabs -= 1
437:     end

Puts a line in @precompiled that will add the closing tag of the most recently opened tag.

[Source]

     # File lib/haml/precompiler.rb, line 391
391:     def close_tag(value)
392:       tag, nuke_outer_whitespace, nuke_inner_whitespace = value
393:       @output_tabs -= 1 unless nuke_inner_whitespace
394:       @template_tabs -= 1
395:       rstrip_buffer! if nuke_inner_whitespace
396:       push_merged_text("</#{tag}>" + (nuke_outer_whitespace ? "" : "\n"),
397:                        nuke_inner_whitespace ? 0 : -1, !nuke_inner_whitespace)
398:       @dont_indent_next_line = nuke_outer_whitespace
399:     end

Concatenate text to @buffer without tabulation.

[Source]

     # File lib/haml/precompiler.rb, line 295
295:     def concat_merged_text(text)
296:       @merged_text  << text
297:     end

[Source]

     # File lib/haml/precompiler.rb, line 730
730:     def contains_interpolation?(str)
731:       str.include?('#{')
732:     end

Counts the tabulation of a line.

[Source]

     # File lib/haml/precompiler.rb, line 767
767:     def count_soft_tabs(line)
768:       spaces = line.index(/([^ ]|$)/)
769:       if line[spaces] == ?\t
770:         return 0, 0 if line.strip.empty?
771:         raise SyntaxError.new("A tab character was used for indentation. Haml must be indented using two spaces.\nAre you sure you have soft tabs enabled in your editor?\n".strip, @next_line.index)
772:       end
773:       [spaces, spaces/2]
774:     end

[Source]

     # File lib/haml/precompiler.rb, line 787
787:     def flat?
788:       @flat_spaces != -1
789:     end

[Source]

     # File lib/haml/precompiler.rb, line 303
303:     def flush_merged_text
304:       return if @merged_text.empty?
305: 
306:       @precompiled  << "_hamlout.push_text(#{@merged_text.dump}"
307:       @precompiled  << ", #{@dont_tab_up_next_text.inspect}" if @dont_tab_up_next_text || @tab_change != 0
308:       @precompiled  << ", #{@tab_change}" if @tab_change != 0
309:       @precompiled  << ");"
310:       @merged_text   = ''
311:       @dont_tab_up_next_text = false
312:       @tab_change    = 0
313:     end

Deals with all the logic of figuring out whether a given line is the beginning, continuation, or end of a multiline sequence.

This returns whether or not the line should be rendered normally.

[Source]

     # File lib/haml/precompiler.rb, line 248
248:     def handle_multiline(line)
249:       text = line.text
250: 
251:       # A multiline string is active, and is being continued
252:       if is_multiline?(text) && @multiline
253:         @multiline.text << text[0...-1]
254:         return true
255:       end
256: 
257:       # A multiline string has just been activated, start adding the lines
258:       if is_multiline?(text) && (MULTILINE_STARTERS.include? text[0])
259:         @multiline = Line.new text[0...-1], nil, line.index, nil, line.tabs
260:         process_indent(line)
261:         return true
262:       end
263: 
264:       # A multiline string has just ended, make line into the result
265:       if @multiline && !line.text.empty?
266:         process_line(@multiline.text, @multiline.index, line.tabs > @multiline.tabs)
267:         @multiline = nil
268:       end
269: 
270:       return false
271:     end

Checks whether or not line is in a multiline sequence.

[Source]

     # File lib/haml/precompiler.rb, line 274
274:     def is_multiline?(text)
275:       text && text.length > 1 && text[-1] == MULTILINE_CHAR_VALUE && text[-2] == ?\s
276:     end

[Source]

     # File lib/haml/precompiler.rb, line 107
107:     def locals_code(names)
108:       names = names.keys if Hash == names
109: 
110:       names.map do |name|
111:         # Can't use || because someone might explicitly pass in false with a symbol
112:         sym_local = "_haml_locals[#{name.to_sym.inspect}]" 
113:         str_local = "_haml_locals[#{name.to_s.inspect}]" 
114:         "#{name} = #{sym_local}.nil? ? #{str_local} : #{sym_local}"
115:       end.join(';') + ';'
116:     end

Returns whether or not the text is a silent script text with one of Ruby‘s mid-block keywords.

[Source]

     # File lib/haml/precompiler.rb, line 239
239:     def mid_block_keyword?(text)
240:       text.length > 2 && text[0] == SILENT_SCRIPT && MID_BLOCK_KEYWORDS.include?(text[1..-1].split[0])
241:     end

[Source]

     # File lib/haml/precompiler.rb, line 791
791:     def newline
792:       @newlines += 1
793:     end

[Source]

     # File lib/haml/precompiler.rb, line 795
795:     def newline_now
796:       @precompiled << "\n"
797:       @newlines -= 1
798:     end

[Source]

     # File lib/haml/precompiler.rb, line 540
540:     def parse_attributes(line)
541:       scanner = StringScanner.new(line)
542:       attributes_hash, rest = balance(scanner, ?{, ?})
543:       attributes_hash = attributes_hash[1...-1] if attributes_hash
544:       return attributes_hash, rest
545:     end

Iterates through the classes and ids supplied through . and # syntax, and returns a hash with them as attributes, that can then be merged with another attributes hash.

[Source]

     # File lib/haml/precompiler.rb, line 442
442:     def parse_class_and_id(list)
443:       attributes = {}
444:       list.scan(/([#.])([-_a-zA-Z0-9]+)/) do |type, property|
445:         case type
446:         when '.'
447:           if attributes['class']
448:             attributes['class'] += " "
449:           else
450:             attributes['class'] = ""
451:           end
452:           attributes['class'] += property
453:         when '#'; attributes['id'] = property
454:         end
455:       end
456:       attributes
457:     end

[Source]

     # File lib/haml/precompiler.rb, line 459
459:     def parse_literal_value(text)
460:       return nil unless text
461:       text.match(LITERAL_VALUE_REGEX)
462: 
463:       # $2 holds the value matched by a symbol, but is nil for a string match
464:       # $5 holds the value matched by a string
465:       $2 || $5
466:     end

[Source]

     # File lib/haml/precompiler.rb, line 468
468:     def parse_static_hash(text)
469:       return {} unless text
470: 
471:       attributes = {}
472:       text.split(',').each do |attrib|
473:         key, value, more = attrib.split('=>')
474: 
475:         # Make sure the key and value and only the key and value exist
476:         # Otherwise, it's too complicated or dynamic and we'll defer it to the actual Ruby parser
477:         key = parse_literal_value key
478:         value = parse_literal_value value
479:         return nil if more || key.nil? || value.nil?
480: 
481:         attributes[key] = value
482:       end
483:       attributes
484:     end

Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value

[Source]

     # File lib/haml/precompiler.rb, line 523
523:     def parse_tag(line)
524:       raise SyntaxError.new("Invalid tag: \"#{line}\".") unless match = line.scan(/%([-:\w]+)([-\w\.\#]*)(.*)/)[0]
525:       tag_name, attributes, rest = match
526:       attributes_hash, rest = parse_attributes(rest) if rest[0] == ?{
527:       if rest
528:         object_ref, rest = balance(rest, ?[, ?]) if rest[0] == ?[
529:         attributes_hash, rest = parse_attributes(rest) if rest[0] == ?{ && attributes_hash.nil?
530:         nuke_whitespace, action, value = rest.scan(/(<>|><|[><])?([=\/\~&!])?(.*)?/)[0]
531:         nuke_whitespace ||= ''
532:         nuke_outer_whitespace = nuke_whitespace.include? '>'
533:         nuke_inner_whitespace = nuke_whitespace.include? '<'
534:       end
535:       value = value.to_s.strip
536:       [tag_name, attributes, attributes_hash, object_ref, nuke_outer_whitespace,
537:        nuke_inner_whitespace, action, value]
538:     end

[Source]

     # File lib/haml/precompiler.rb, line 120
120:     def precompile
121:       @haml_comment = @dont_indent_next_line = @dont_tab_up_next_text = false
122:       @indentation = nil
123:       old_line = Line.new
124:       @template.split(/\r\n|\r|\n/).each_with_index do |text, index|
125:         @next_line = line = Line.new(text.strip, text.lstrip.chomp, index)
126:         line.spaces, line.tabs = count_soft_tabs(text)
127: 
128:         suppress_render = handle_multiline(old_line) unless flat?
129: 
130:         if old_line.text.nil? || suppress_render
131:           old_line = line
132:           resolve_newlines
133:           newline
134:           next
135:         end
136: 
137:         process_indent(old_line) unless old_line.text.empty?
138: 
139:         if line.text.empty? && !flat?
140:           newline
141:           next
142:         end
143: 
144:         if flat?
145:           push_flat(old_line)
146:           old_line = line
147:           newline
148:           next
149:         end
150: 
151:         if old_line.spaces != old_line.tabs * 2
152:           raise SyntaxError.new("\#{old_line.spaces} space\#{old_line.spaces == 1 ? ' was' : 's were'} used for indentation. Haml must be indented using two spaces.\n".strip, old_line.index)
153:         end
154: 
155:         unless old_line.text.empty? || @haml_comment
156:           process_line(old_line.text, old_line.index, line.tabs > old_line.tabs && !line.text.empty?)
157:         end
158:         resolve_newlines
159: 
160:         if !flat? && line.tabs - old_line.tabs > 1
161:           raise SyntaxError.new("\#{line.spaces} spaces were used for indentation. Haml must be indented using two spaces.\n".strip, line.index)
162:         end
163:         old_line = line
164:         newline
165:       end
166: 
167:       # Close all the open tags
168:       close until @to_close_stack.empty?
169:       flush_merged_text
170:     end

Returns the precompiled string with the preamble and postamble

[Source]

    # File lib/haml/precompiler.rb, line 91
91:     def precompiled_with_ambles(local_names)
92:       preamble = "extend Haml::Helpers\n_hamlout = @haml_buffer = Haml::Buffer.new(@haml_buffer, \#{options_for_buffer.inspect})\n_erbout = _hamlout.buffer\n__in_erb_template = true\n".gsub("\n", ";")
93:       postamble = "@haml_buffer = @haml_buffer.upper\n_erbout\n".gsub("\n", ";")
94:       preamble + locals_code(local_names) + @precompiled + postamble
95:     end

[Source]

     # File lib/haml/precompiler.rb, line 517
517:     def prerender_tag(name, self_close, attributes)
518:       attributes_string = Precompiler.build_attributes(html?, @options[:attr_wrapper], attributes)
519:       "<#{name}#{attributes_string}#{self_close && xhtml? ? ' /' : ''}>"
520:     end

Processes and deals with lowering indentation.

[Source]

     # File lib/haml/precompiler.rb, line 179
179:     def process_indent(line)
180:       return unless line.tabs <= @template_tabs && @template_tabs > 0
181: 
182:       to_close = @template_tabs - line.tabs
183:       to_close.times { |i| close unless to_close - 1 - i == 0 && mid_block_keyword?(line.text) }
184:     end

Processes a single line of Haml.

This method doesn‘t return anything; it simply processes the line and adds the appropriate code to @precompiled.

[Source]

     # File lib/haml/precompiler.rb, line 190
190:     def process_line(text, index, block_opened)
191:       @block_opened = block_opened
192:       @index = index + 1
193: 
194:       case text[0]
195:       when DIV_CLASS, DIV_ID; render_div(text)
196:       when ELEMENT; render_tag(text)
197:       when COMMENT; render_comment(text[1..-1].strip)
198:       when SANITIZE
199:         return push_script(unescape_interpolation(text[3..-1].strip), false, false, false, true) if text[1..2] == "=="
200:         return push_script(text[2..-1].strip, false, false, false, true) if text[1] == SCRIPT
201:         push_plain text
202:       when SCRIPT
203:         return push_script(unescape_interpolation(text[2..-1].strip), false) if text[1] == SCRIPT
204:         return push_script(text[1..-1], false, false, false, true) if options[:escape_html]
205:         push_script(text[1..-1], false)
206:       when FLAT_SCRIPT; push_flat_script(text[1..-1])
207:       when SILENT_SCRIPT
208:         return start_haml_comment if text[1] == SILENT_COMMENT
209: 
210:         raise SyntaxError.new("You don't need to use \"- end\" in Haml. Use indentation instead:\n- if foo?\n  %strong Foo!\n- else\n  Not foo.\n".rstrip, index) if text[1..-1].strip == "end"
211: 
212:         push_silent(text[1..-1], true)
213:         newline_now
214: 
215:         case_stmt = text[1..-1].split(' ', 2)[0] == "case"
216:         block = @block_opened && !mid_block_keyword?(text)
217:         push_and_tabulate([:script]) if block || case_stmt
218:         push_and_tabulate(nil)       if block && case_stmt
219:       when FILTER; start_filtered(text[1..-1].downcase)
220:       when DOCTYPE
221:         return render_doctype(text) if text[0...3] == '!!!'
222:         return push_script(unescape_interpolation(text[3..-1].strip), false) if text[1..2] == "=="
223:         return push_script(text[2..-1].strip, false) if text[1] == SCRIPT
224:         push_plain text
225:       when ESCAPE; push_plain text[1..-1]
226:       else push_plain text
227:       end
228:     end

Pushes value onto @to_close_stack and increases @template_tabs.

[Source]

     # File lib/haml/precompiler.rb, line 782
782:     def push_and_tabulate(value)
783:       @to_close_stack.push(value)
784:       @template_tabs += 1
785:     end

Adds text to @buffer while flattening text.

[Source]

     # File lib/haml/precompiler.rb, line 326
326:     def push_flat(line)
327:       tabulation = line.spaces - @flat_spaces
328:       tabulation = tabulation > -1 ? tabulation : 0
329:       @filter_buffer << "#{' ' * tabulation}#{line.unstripped}\n"
330:     end

Causes text to be evaluated, and Haml::Helpers#find_and_flatten to be run on it afterwards.

[Source]

     # File lib/haml/precompiler.rb, line 361
361:     def push_flat_script(text)
362:       flush_merged_text
363: 
364:       raise SyntaxError.new("There's no Ruby code for ~ to evaluate.") if text.empty?
365:       push_script(text, true)
366:     end

Adds text to @buffer with appropriate tabulation without parsing it.

[Source]

     # File lib/haml/precompiler.rb, line 288
288:     def push_merged_text(text, tab_change = 0, indent = true)
289:       @merged_text  << (!indent || @dont_indent_next_line || @options[:ugly] ? text : "#{'  ' * @output_tabs}#{text}")
290:       @dont_indent_next_line = false
291:       @tab_change   += tab_change
292:     end

Renders a block of text as plain text. Also checks for an illegally opened block.

[Source]

     # File lib/haml/precompiler.rb, line 317
317:     def push_plain(text)
318:       if @block_opened
319:         raise SyntaxError.new("Illegal nesting: nesting within plain text is illegal.", @next_line.index)
320:       end
321: 
322:       push_text text
323:     end

Causes text to be evaluated in the context of the scope object and the result to be added to @buffer.

If preserve_script is true, Haml::Helpers#find_and_flatten is run on the result before it is added to @buffer

[Source]

     # File lib/haml/precompiler.rb, line 337
337:     def push_script(text, preserve_script, in_tag = false, preserve_tag = false,
338:                     escape_html = false, nuke_inner_whitespace = false)
339:       # Prerender tabulation unless we're in a tag
340:       push_merged_text '' unless in_tag
341: 
342:       flush_merged_text
343:       return if options[:suppress_eval]
344: 
345:       raise SyntaxError.new("There's no Ruby code for = to evaluate.") if text.empty?
346: 
347:       push_silent "haml_temp = #{text}"
348:       newline_now
349:       args = [preserve_script, in_tag, preserve_tag,
350:               escape_html, nuke_inner_whitespace].map { |a| a.inspect }.join(', ')
351:       out = "haml_temp = _hamlout.push_script(haml_temp, #{args});"
352:       if @block_opened
353:         push_and_tabulate([:loud, out])
354:       else
355:         @precompiled << out
356:       end
357:     end

Evaluates text in the context of the scope object, but does not output the result.

[Source]

     # File lib/haml/precompiler.rb, line 280
280:     def push_silent(text, can_suppress = false)
281:       flush_merged_text
282:       return if can_suppress && options[:suppress_eval]
283:       @precompiled << "#{text};"
284:     end

[Source]

     # File lib/haml/precompiler.rb, line 299
299:     def push_text(text, tab_change = 0)
300:       push_merged_text("#{text}\n", tab_change)
301:     end

Renders an XHTML comment.

[Source]

     # File lib/haml/precompiler.rb, line 651
651:     def render_comment(line)
652:       conditional, line = balance(line, ?[, ?]) if line[0] == ?[
653:       line.strip!
654:       conditional << ">" if conditional
655: 
656:       if @block_opened && !line.empty?
657:         raise SyntaxError.new('Illegal nesting: nesting within a tag that already has content is illegal.', @next_line.index)
658:       end
659: 
660:       open = "<!--#{conditional} "
661: 
662:       # Render it statically if possible
663:       unless line.empty?
664:         return push_text("#{open}#{line} #{conditional ? "<![endif]-->" : "-->"}")
665:       end
666: 
667:       push_text(open, 1)
668:       @output_tabs += 1
669:       push_and_tabulate([:comment, !conditional.nil?])
670:       unless line.empty?
671:         push_text(line)
672:         close
673:       end
674:     end

Renders a line that creates an XHTML tag and has an implicit div because of . or #.

[Source]

     # File lib/haml/precompiler.rb, line 646
646:     def render_div(line)
647:       render_tag('%div' + line)
648:     end

Renders an XHTML doctype or XML shebang.

[Source]

     # File lib/haml/precompiler.rb, line 677
677:     def render_doctype(line)
678:       raise SyntaxError.new("Illegal nesting: nesting within a header command is illegal.", @next_line.index) if @block_opened
679:       doctype = text_for_doctype(line)
680:       push_text doctype if doctype
681:     end

Parses a line that will render as an XHTML tag, and adds the code that will render that tag to @precompiled.

[Source]

     # File lib/haml/precompiler.rb, line 549
549:     def render_tag(line)
550:       tag_name, attributes, attributes_hash, object_ref, nuke_outer_whitespace,
551:         nuke_inner_whitespace, action, value = parse_tag(line)
552: 
553:       raise SyntaxError.new("Illegal element: classes and ids must have values.") if attributes =~ /[\.#](\.|#|\z)/
554: 
555:       # Get rid of whitespace outside of the tag if we need to
556:       rstrip_buffer! if nuke_outer_whitespace
557: 
558:       preserve_tag = options[:preserve].include?(tag_name)
559:       nuke_inner_whitespace ||= preserve_tag
560:       preserve_tag &&= !options[:ugly]
561: 
562:       case action
563:       when '/'; self_closing = true
564:       when '~'; parse = preserve_script = true
565:       when '='
566:         parse = true
567:         value = unescape_interpolation(value[1..-1].strip) if value[0] == ?=
568:       when '&', '!'
569:         if value[0] == ?=
570:           parse = true
571:           value = (value[1] == ?= ? unescape_interpolation(value[2..-1].strip) : value[1..-1].strip)
572:         end
573:       end
574: 
575:       if parse && @options[:suppress_eval]
576:         parse = false
577:         value = ''
578:       end
579: 
580:       escape_html = (action == '&' || (action != '!' && @options[:escape_html]))
581: 
582:       object_ref = "nil" if object_ref.nil? || @options[:suppress_eval]
583: 
584:       static_attributes = parse_static_hash(attributes_hash) # Try pre-compiling a static attributes hash
585:       attributes_hash = nil if static_attributes || @options[:suppress_eval]
586:       attributes = parse_class_and_id(attributes)
587:       Buffer.merge_attrs(attributes, static_attributes) if static_attributes
588: 
589:       raise SyntaxError.new("Illegal nesting: nesting within a self-closing tag is illegal.", @next_line.index) if @block_opened && self_closing
590:       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) if @block_opened && !value.empty?
591:       raise SyntaxError.new("There's no Ruby code for #{action} to evaluate.") if parse && value.empty?
592:       raise SyntaxError.new("Self-closing tags can't have content.") if self_closing && !value.empty?
593: 
594:       self_closing ||= !!( !@block_opened && value.empty? && @options[:autoclose].include?(tag_name) )
595: 
596:       dont_indent_next_line =
597:         (nuke_outer_whitespace && !@block_opened) ||
598:         (nuke_inner_whitespace && @block_opened)
599: 
600:       # Check if we can render the tag directly to text and not process it in the buffer
601:       if object_ref == "nil" && attributes_hash.nil? && !preserve_script
602:         tag_closed = !@block_opened && !self_closing && !parse
603: 
604:         open_tag  = prerender_tag(tag_name, self_closing, attributes)
605:         if tag_closed
606:           open_tag << "#{value}</#{tag_name}>"
607:           open_tag << "\n" unless nuke_outer_whitespace
608:         else
609:           open_tag << "\n" unless parse || nuke_inner_whitespace || (self_closing && nuke_outer_whitespace)
610:         end
611:         
612:         push_merged_text(open_tag, tag_closed || self_closing || nuke_inner_whitespace ? 0 : 1,
613:                          !nuke_outer_whitespace)
614: 
615:         @dont_indent_next_line = dont_indent_next_line
616:         return if tag_closed
617:       else
618:         flush_merged_text
619:         content = value.empty? || parse ? 'nil' : value.dump
620:         attributes_hash = ', ' + attributes_hash if attributes_hash
621:         args = [tag_name, self_closing, !@block_opened, preserve_tag, escape_html,
622:                 attributes, nuke_outer_whitespace, nuke_inner_whitespace
623:                ].map { |v| v.inspect }.join(', ')
624:         push_silent "_hamlout.open_tag(#{args}, #{object_ref}, #{content}#{attributes_hash})"
625:         @dont_tab_up_next_text = @dont_indent_next_line = dont_indent_next_line
626:       end
627: 
628:       return if self_closing
629: 
630:       if value.empty?
631:         push_and_tabulate([:element, [tag_name, nuke_outer_whitespace, nuke_inner_whitespace]])
632:         @output_tabs += 1 unless nuke_inner_whitespace
633:         return
634:       end
635: 
636:       if parse
637:         flush_merged_text
638:         push_script(value, preserve_script, true, preserve_tag, escape_html, nuke_inner_whitespace)
639:         @dont_tab_up_next_text = true
640:         concat_merged_text("</#{tag_name}>" + (nuke_outer_whitespace ? "" : "\n"))
641:       end
642:     end

[Source]

     # File lib/haml/precompiler.rb, line 800
800:     def resolve_newlines
801:       return unless @newlines > 0
802:       @precompiled << "\n" * @newlines
803:       @newlines = 0
804:     end

Get rid of and whitespace at the end of the buffer or the merged text

[Source]

     # File lib/haml/precompiler.rb, line 808
808:     def rstrip_buffer!
809:       unless @merged_text.empty?
810:         @merged_text.rstrip!
811:       else
812:         push_silent("_erbout.rstrip!", false)
813:         @dont_tab_up_next_text = true
814:       end
815:     end

Starts a filtered block.

[Source]

     # File lib/haml/precompiler.rb, line 720
720:     def start_filtered(name)
721:       raise Error.new("Invalid filter name \":#{name}\".") unless name =~ /^\w+$/
722:       raise Error.new("Filter \"#{name}\" is not defined.") unless filter = Filters.defined[name]
723: 
724:       push_and_tabulate([:filtered, filter])
725:       @flat_spaces = @template_tabs * 2
726:       @filter_buffer = String.new
727:       @block_opened = false
728:     end

[Source]

     # File lib/haml/precompiler.rb, line 368
368:     def start_haml_comment
369:       return unless @block_opened
370: 
371:       @haml_comment = true
372:       push_and_tabulate([:haml_comment])
373:     end

[Source]

     # File lib/haml/precompiler.rb, line 683
683:     def text_for_doctype(text)
684:       text = text[3..-1].lstrip.downcase
685:       if text.index("xml") == 0
686:         return nil if html?
687:         wrapper = @options[:attr_wrapper]
688:         return "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{text.split(' ')[1] || "utf-8"}#{wrapper} ?>"
689:       end
690: 
691:       if html5?
692:         '<!DOCTYPE html>'
693:       else
694:         version, type = text.scan(DOCTYPE_REGEX)[0]
695: 
696:         if xhtml?
697:           if version == "1.1"
698:             '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
699:           else
700:             case type
701:             when "strict";   '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
702:             when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
703:             when "mobile";   '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
704:             when "basic";    '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
705:             else             '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
706:             end
707:           end
708: 
709:         elsif html4?
710:           case type
711:           when "strict";   '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
712:           when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
713:           else             '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
714:           end
715:         end
716:       end
717:     end

[Source]

     # File lib/haml/precompiler.rb, line 734
734:     def unescape_interpolation(str)
735:       scan = StringScanner.new(str.dump)
736:       str = ''
737: 
738:       while scan.scan(/(.*?)(\\+)\#\{/)
739:         escapes = (scan[2].size - 1) / 2
740:         str << scan.matched[0...-3 - escapes]
741:         if escapes % 2 == 1
742:           str << '#{'
743:         else
744:           # Use eval to get rid of string escapes
745:           str << '#{' + eval('"' + balance(scan, ?{, ?}, 1)[0][0...-1] + '"') + "}"
746:         end
747:       end
748: 
749:       str + scan.rest
750:     end

[Validate]