Module Haml::Precompiler
In: lib/haml/template/plugin.rb
lib/haml/precompiler.rb

Handles the internal pre-compilation from Haml into Ruby code, which then runs the final creation of the HTML string.

Methods

Included Modules

Haml::Util

Classes and Modules

Class Haml::Precompiler::Line

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.
MID_BLOCK_KEYWORD_REGEX = /^-\s*(#{%w[else elsif rescue ensure when end].join('|')})\b/   Regex to match 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 = /:(\w*)|(["'])((?![\\#]|\2).|\\.)*\2/   The Regex that matches a literal string or symbol value

External Aliases

push_silent -> push_silent_without_haml_block_deprecation

Private Class methods

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

[Source]

     # File lib/haml/precompiler.rb, line 529
529:     def self.build_attributes(is_html, attr_wrapper, attributes = {})
530:       quote_escape = attr_wrapper == '"' ? "&quot;" : "&apos;"
531:       other_quote_char = attr_wrapper == '"' ? "'" : '"'
532: 
533:       if attributes['data'].is_a?(Hash)
534:         attributes = attributes.dup
535:         attributes =
536:           Haml::Util.map_keys(attributes.delete('data')) {|name| "data-#{name}"}.merge(attributes)
537:       end
538: 
539:       result = attributes.collect do |attr, value|
540:         next if value.nil?
541: 
542:         value = filter_and_join(value, ' ') if attr == :class
543:         value = filter_and_join(value, '_') if attr == :id
544: 
545:         if value == true
546:           next " #{attr}" if is_html
547:           next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
548:         elsif value == false
549:           next
550:         end
551: 
552:         value = Haml::Helpers.preserve(Haml::Helpers.escape_once(value.to_s))
553:         # We want to decide whether or not to escape quotes
554:         value.gsub!('&quot;', '"')
555:         this_attr_wrapper = attr_wrapper
556:         if value.include? attr_wrapper
557:           if value.include? other_quote_char
558:             value = value.gsub(attr_wrapper, quote_escape)
559:           else
560:             this_attr_wrapper = other_quote_char
561:           end
562:         end
563:         " #{attr}=#{this_attr_wrapper}#{value}#{this_attr_wrapper}"
564:       end
565:       result.compact.sort.join
566:     end

[Source]

     # File lib/haml/precompiler.rb, line 568
568:     def self.filter_and_join(value, separator)
569:       value = [value] unless value.is_a?(Array)
570:       return value.flatten.collect {|item| item ? item.to_s : nil}.compact.join(separator)
571:     end

This is a class method so it can be accessed from {Haml::Helpers}.

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 496
496:     def self.parse_class_and_id(list)
497:       attributes = {}
498:       list.scan(/([#.])([-_a-zA-Z0-9]+)/) do |type, property|
499:         case type
500:         when '.'
501:           if attributes['class']
502:             attributes['class'] += " "
503:           else
504:             attributes['class'] = ""
505:           end
506:           attributes['class'] += property
507:         when '#'; attributes['id'] = property
508:         end
509:       end
510:       attributes
511:     end

Public Instance methods

push_silent(text, can_suppress = false)

[Source]

    # File lib/haml/template/plugin.rb, line 49
49:       def push_silent_with_haml_block_deprecation(text, can_suppress = false)
50:         unless can_suppress && block_opened? && !mid_block_keyword?("- #{text}") &&
51:             text =~ ActionView::Template::Handlers::Erubis::BLOCK_EXPR
52:           return push_silent_without_haml_block_deprecation(text, can_suppress)
53:         end
54: 
55:         push_silent_without_haml_block_deprecation("_hamlout.append_if_string= #{text}", can_suppress)
56:       end

Private Instance methods

[Source]

      # File lib/haml/precompiler.rb, line 1037
1037:     def balance(*args)
1038:       res = Haml::Shared.balance(*args)
1039:       return res if res
1040:       raise SyntaxError.new("Unbalanced brackets.")
1041:     end

[Source]

      # File lib/haml/precompiler.rb, line 1043
1043:     def block_opened?
1044:       !flat? && @next_line.tabs > @line.tabs
1045:     end

Closes the most recent item in `@to_close_stack`.

[Source]

     # File lib/haml/precompiler.rb, line 434
434:     def close
435:       tag, *rest = @to_close_stack.pop
436:       send("close_#{tag}", *rest)
437:     end

Closes a comment.

[Source]

     # File lib/haml/precompiler.rb, line 458
458:     def close_comment(has_conditional)
459:       @output_tabs -= 1
460:       @template_tabs -= 1
461:       close_tag = has_conditional ? "<![endif]-->" : "-->"
462:       push_text(close_tag, -1)
463:     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 441
441:     def close_element(value)
442:       tag, nuke_outer_whitespace, nuke_inner_whitespace = value
443:       @output_tabs -= 1 unless nuke_inner_whitespace
444:       @template_tabs -= 1
445:       rstrip_buffer! if nuke_inner_whitespace
446:       push_merged_text("</#{tag}>" + (nuke_outer_whitespace ? "" : "\n"),
447:                        nuke_inner_whitespace ? 0 : -1, !nuke_inner_whitespace)
448:       @dont_indent_next_line = nuke_outer_whitespace
449:     end

Closes a filtered block.

[Source]

     # File lib/haml/precompiler.rb, line 474
474:     def close_filtered(filter)
475:       filter.internal_compile(self, @filter_buffer)
476:       @flat = false
477:       @flat_spaces = nil
478:       @filter_buffer = nil
479:       @template_tabs -= 1
480:     end

[Source]

     # File lib/haml/precompiler.rb, line 482
482:     def close_haml_comment
483:       @haml_comment = false
484:       @template_tabs -= 1
485:     end

Closes a loud Ruby block.

[Source]

     # File lib/haml/precompiler.rb, line 466
466:     def close_loud(command, add_newline, push_end = true)
467:       push_silent('end', true) if push_end
468:       @precompiled << command
469:       @template_tabs -= 1
470:       concat_merged_text("\n") if add_newline
471:     end

[Source]

     # File lib/haml/precompiler.rb, line 487
487:     def close_nil(*args)
488:       @template_tabs -= 1
489:     end

Closes a Ruby block.

[Source]

     # File lib/haml/precompiler.rb, line 452
452:     def close_script(_1, _2, push_end = true)
453:       push_silent("end", true) if push_end
454:       @template_tabs -= 1
455:     end

[Source]

     # File lib/haml/precompiler.rb, line 969
969:     def closes_flat?(line)
970:       line && !line.text.empty? && line.full !~ /^#{@flat_spaces}/
971:     end

Concatenate `text` to `@buffer` without tabulation.

[Source]

     # File lib/haml/precompiler.rb, line 311
311:     def concat_merged_text(text)
312:       @to_merge << [:text, text, 0]
313:     end

[Source]

      # File lib/haml/precompiler.rb, line 1017
1017:     def contains_interpolation?(str)
1018:       str.include?('#{')
1019:     end

[Source]

      # File lib/haml/precompiler.rb, line 1054
1054:     def flat?
1055:       @flat
1056:     end

[Source]

     # File lib/haml/precompiler.rb, line 319
319:     def flush_merged_text
320:       return if @to_merge.empty?
321: 
322:       str = ""
323:       mtabs = 0
324:       newlines = 0
325:       @to_merge.each do |type, val, tabs|
326:         case type
327:         when :text
328:           str << val.inspect[1...-1]
329:           mtabs += tabs
330:         when :script
331:           if mtabs != 0 && !@options[:ugly]
332:             val = "_hamlout.adjust_tabs(#{mtabs}); " + val
333:           end
334:           str << "\#{#{"\n" * newlines}#{val}}"
335:           mtabs = 0
336:           newlines = 0
337:         when :newlines
338:           newlines += val
339:         else
340:           raise SyntaxError.new("[HAML BUG] Undefined entry in Haml::Precompiler@to_merge.")
341:         end
342:       end
343: 
344:       @precompiled <<
345:         if @options[:ugly]
346:           "_hamlout.buffer << \"#{str}\";"
347:         else
348:           "_hamlout.push_text(\"#{str}\", #{mtabs}, #{@dont_tab_up_next_text.inspect});"
349:         end
350:       @precompiled << "\n" * newlines
351:       @to_merge = []
352:       @dont_tab_up_next_text = false
353:     end

[Source]

     # File lib/haml/precompiler.rb, line 978
978:     def handle_multiline(line)
979:       return unless is_multiline?(line.text)
980:       line.text.slice!(-1)
981:       while new_line = raw_next_line.first
982:         break if new_line == :eod
983:         newline and next if new_line.strip.empty?
984:         break unless is_multiline?(new_line.strip)
985:         line.text << new_line.strip[0...-1]
986:         newline
987:       end
988:       un_next_line new_line
989:       resolve_newlines
990:     end

[Source]

      # File lib/haml/precompiler.rb, line 997
 997:     def handle_ruby_multiline(text)
 998:       text = text.rstrip
 999:       return text unless is_ruby_multiline?(text)
1000:       un_next_line @next_line.full
1001:       begin
1002:         new_line = raw_next_line.first
1003:         break if new_line == :eod
1004:         newline and next if new_line.strip.empty?
1005:         text << " " << new_line.strip
1006:         newline
1007:       end while is_ruby_multiline?(new_line.strip)
1008:       next_line
1009:       resolve_newlines
1010:       text
1011:     end

Checks whether or not line is in a multiline sequence.

[Source]

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

[Source]

      # File lib/haml/precompiler.rb, line 1013
1013:     def is_ruby_multiline?(text)
1014:       text && text.length > 1 && text[-1] == ?, && text[-2] != ?? && text[-3..-2] != "?\\"
1015:     end

[Source]

     # File lib/haml/precompiler.rb, line 118
118:     def locals_code(names)
119:       names = names.keys if Hash == names
120: 
121:       names.map do |name|
122:         # Can't use || because someone might explicitly pass in false with a symbol
123:         sym_local = "_haml_locals[#{name.to_sym.inspect}]"
124:         str_local = "_haml_locals[#{name.to_s.inspect}]"
125:         "#{name} = #{sym_local}.nil? ? #{str_local} : #{sym_local}"
126:       end.join(';') + ';'
127:     end

If the text is a silent script text with one of Ruby‘s mid-block keywords, returns the name of that keyword. Otherwise, returns nil.

[Source]

     # File lib/haml/precompiler.rb, line 290
290:     def mid_block_keyword?(text)
291:       text[MID_BLOCK_KEYWORD_REGEX, 1]
292:     end

[Source]

      # File lib/haml/precompiler.rb, line 1058
1058:     def newline
1059:       @newlines += 1
1060:     end

[Source]

      # File lib/haml/precompiler.rb, line 1062
1062:     def newline_now
1063:       @precompiled << "\n"
1064:       @newlines -= 1
1065:     end

[Source]

     # File lib/haml/precompiler.rb, line 939
939:     def next_line
940:       text, index = raw_next_line
941:       return unless text
942: 
943:       # :eod is a special end-of-document marker
944:       line =
945:         if text == :eod
946:           Line.new '-#', '-#', '-#', index, self, true
947:         else
948:           Line.new text.strip, text.lstrip.chomp, text, index, self, false
949:         end
950: 
951:       # `flat?' here is a little outdated,
952:       # so we have to manually check if either the previous or current line
953:       # closes the flat block,
954:       # as well as whether a new block is opened
955:       @line.tabs if @line
956:       unless (flat? && !closes_flat?(line) && !closes_flat?(@line)) ||
957:           (@line && @line.text[0] == ?: && line.full =~ %r[^#{@line.full[/^\s+/]}\s])
958:         if line.text.empty?
959:           newline
960:           return next_line
961:         end
962: 
963:         handle_multiline(line)
964:       end
965: 
966:       @next_line = line
967:     end

[Source]

     # File lib/haml/precompiler.rb, line 675
675:     def parse_new_attribute(scanner)
676:       unless name = scanner.scan(/[-:\w]+/)
677:         return if scanner.scan(/\)/)
678:         return false
679:       end
680: 
681:       scanner.scan(/\s*/)
682:       return name, [:static, true] unless scanner.scan(/=/) #/end
683: 
684:       scanner.scan(/\s*/)
685:       unless quote = scanner.scan(/["']/)
686:         return false unless var = scanner.scan(/(@@?|\$)?\w+/)
687:         return name, [:dynamic, var]
688:       end
689: 
690:       re = /((?:\\.|\#(?!\{)|[^#{quote}\\#])*)(#{quote}|#\{)/
691:       content = []
692:       loop do
693:         return false unless scanner.scan(re)
694:         content << [:str, scanner[1].gsub(/\\(.)/, '\1')]
695:         break if scanner[2] == quote
696:         content << [:ruby, balance(scanner, ?{, ?}, 1).first[0...-1]]
697:       end
698: 
699:       return name, [:static, content.first[1]] if content.size == 1
700:       return name, [:dynamic,
701:         '"' + content.map {|(t, v)| t == :str ? v.inspect[1...-1] : "\#{#{v}}"}.join + '"']
702:     end

[Source]

     # File lib/haml/precompiler.rb, line 634
634:     def parse_new_attributes(line)
635:       line = line.dup
636:       scanner = StringScanner.new(line)
637:       last_line = @index
638:       attributes = {}
639: 
640:       scanner.scan(/\(\s*/)
641:       loop do
642:         name, value = parse_new_attribute(scanner)
643:         break if name.nil?
644: 
645:         if name == false
646:           text = (Haml::Shared.balance(line, ?(, ?)) || [line]).first
647:           raise Haml::SyntaxError.new("Invalid attribute list: #{text.inspect}.", last_line - 1)
648:         end
649:         attributes[name] = value
650:         scanner.scan(/\s*/)
651: 
652:         if scanner.eos?
653:           line << " " << @next_line.text
654:           last_line += 1
655:           next_line
656:           scanner.scan(/\s*/)
657:         end
658:       end
659: 
660:       static_attributes = {}
661:       dynamic_attributes = "{"
662:       attributes.each do |name, (type, val)|
663:         if type == :static
664:           static_attributes[name] = val
665:         else
666:           dynamic_attributes << name.inspect << " => " << val << ","
667:         end
668:       end
669:       dynamic_attributes << "}"
670:       dynamic_attributes = nil if dynamic_attributes == "{}"
671: 
672:       return [static_attributes, dynamic_attributes], scanner.rest, last_line
673:     end

[Source]

     # File lib/haml/precompiler.rb, line 613
613:     def parse_old_attributes(line)
614:       line = line.dup
615:       last_line = @index
616: 
617:       begin
618:         attributes_hash, rest = balance(line, ?{, ?})
619:       rescue SyntaxError => e
620:         if line.strip[-1] == ?, && e.message == "Unbalanced brackets."
621:           line << "\n" << @next_line.text
622:           last_line += 1
623:           next_line
624:           retry
625:         end
626: 
627:         raise e
628:       end
629: 
630:       attributes_hash = attributes_hash[1...-1] if attributes_hash
631:       return attributes_hash, rest, last_line
632:     end

[Source]

     # File lib/haml/precompiler.rb, line 513
513:     def parse_static_hash(text)
514:       attributes = {}
515:       scanner = StringScanner.new(text)
516:       scanner.scan(/\s+/)
517:       until scanner.eos?
518:         return unless key = scanner.scan(LITERAL_VALUE_REGEX)
519:         return unless scanner.scan(/\s*=>\s*/)
520:         return unless value = scanner.scan(LITERAL_VALUE_REGEX)
521:         return unless scanner.scan(/\s*(?:,|$)\s*/)
522:         attributes[eval(key).to_s] = eval(value).to_s
523:       end
524:       text.count("\n").times { newline }
525:       attributes
526:     end

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

[Source]

     # File lib/haml/precompiler.rb, line 579
579:     def parse_tag(line)
580:       raise SyntaxError.new("Invalid tag: \"#{line}\".") unless match = line.scan(/%([-:\w]+)([-\w\.\#]*)(.*)/)[0]
581:       tag_name, attributes, rest = match
582:       new_attributes_hash = old_attributes_hash = last_line = object_ref = nil
583:       attributes_hashes = []
584:       while rest
585:         case rest[0]
586:         when ?{
587:           break if old_attributes_hash
588:           old_attributes_hash, rest, last_line = parse_old_attributes(rest)
589:           attributes_hashes << [:old, old_attributes_hash]
590:         when ?(
591:           break if new_attributes_hash
592:           new_attributes_hash, rest, last_line = parse_new_attributes(rest)
593:           attributes_hashes << [:new, new_attributes_hash]
594:         when ?[
595:           break if object_ref
596:           object_ref, rest = balance(rest, ?[, ?])
597:         else; break
598:         end
599:       end
600: 
601:       if rest
602:         nuke_whitespace, action, value = rest.scan(/(<>|><|[><])?([=\/\~&!])?(.*)?/)[0]
603:         nuke_whitespace ||= ''
604:         nuke_outer_whitespace = nuke_whitespace.include? '>'
605:         nuke_inner_whitespace = nuke_whitespace.include? '<'
606:       end
607: 
608:       value = value.to_s.strip
609:       [tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
610:        nuke_inner_whitespace, action, value, last_line || @index]
611:     end

[Source]

     # File lib/haml/precompiler.rb, line 163
163:     def precompile
164:       @haml_comment = @dont_indent_next_line = @dont_tab_up_next_text = false
165:       @indentation = nil
166:       @line = next_line
167:       resolve_newlines
168:       newline
169: 
170:       raise SyntaxError.new("Indenting at the beginning of the document is illegal.", @line.index) if @line.tabs != 0
171: 
172:       while next_line
173:         process_indent(@line) unless @line.text.empty?
174: 
175:         if flat?
176:           push_flat(@line)
177:           @line = @next_line
178:           next
179:         end
180: 
181:         process_line(@line.text, @line.index) unless @line.text.empty? || @haml_comment
182: 
183:         if !flat? && @next_line.tabs - @line.tabs > 1
184:           raise SyntaxError.new("The line was indented #{@next_line.tabs - @line.tabs} levels deeper than the previous line.", @next_line.index)
185:         end
186: 
187:         resolve_newlines unless @next_line.eod?
188:         @line = @next_line
189:         newline unless @next_line.eod?
190:       end
191: 
192:       # Close all the open tags
193:       close until @to_close_stack.empty?
194:       flush_merged_text
195:     end

Returns the string used as the return value of the precompiled method. This method exists so it can be monkeypatched to return modified values.

[Source]

     # File lib/haml/precompiler.rb, line 114
114:     def precompiled_method_return_value
115:       "_erbout"
116:     end

Returns the precompiled string with the preamble and postamble

[Source]

    # File lib/haml/precompiler.rb, line 93
93:     def precompiled_with_ambles(local_names)
94:       preamble = "begin\nextend 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", ";")
95:       postamble = "\#{precompiled_method_return_value}\nensure\n@haml_buffer = @haml_buffer.upper\nend\n".gsub("\n", ";")
96:       preamble + locals_code(local_names) + precompiled + postamble
97:     end

[Source]

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

Processes and deals with lowering indentation.

[Source]

     # File lib/haml/precompiler.rb, line 198
198:     def process_indent(line)
199:       return unless line.tabs <= @template_tabs && @template_tabs > 0
200: 
201:       to_close = @template_tabs - line.tabs
202:       to_close.times {|i| close unless to_close - 1 - i == 0 && mid_block_keyword?(line.text)}
203:     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 209
209:     def process_line(text, index)
210:       @index = index + 1
211: 
212:       case text[0]
213:       when DIV_CLASS; render_div(text)
214:       when DIV_ID
215:         return push_plain(text) if text[1] == ?{
216:         render_div(text)
217:       when ELEMENT; render_tag(text)
218:       when COMMENT; render_comment(text[1..-1].strip)
219:       when SANITIZE
220:         return push_plain(text[3..-1].strip, :escape_html => true) if text[1..2] == "=="
221:         return push_script(text[2..-1].strip, :escape_html => true) if text[1] == SCRIPT
222:         return push_flat_script(text[2..-1].strip, :escape_html => true) if text[1] == FLAT_SCRIPT
223:         return push_plain(text[1..-1].strip, :escape_html => true) if text[1] == ?\s
224:         push_plain text
225:       when SCRIPT
226:         return push_plain(text[2..-1].strip) if text[1] == SCRIPT
227:         push_script(text[1..-1])
228:       when FLAT_SCRIPT; push_flat_script(text[1..-1])
229:       when SILENT_SCRIPT
230:         return start_haml_comment if text[1] == SILENT_COMMENT
231: 
232:         raise SyntaxError.new("You don't need to use \"- end\" in Haml. Un-indent to close a block:\n- if foo?\n  %strong Foo!\n- else\n  Not foo.\n%p This line is un-indented, so it isn't part of the \"if\" block\n".rstrip, index) if text[1..-1].strip == "end"
233: 
234:         text = handle_ruby_multiline(text)
235:         push_silent(text[1..-1], true)
236:         newline_now
237: 
238:         # Handle stuff like - end.join("|")
239:         @to_close_stack.last << false if text =~ /^-\s*end\b/ && !block_opened?
240: 
241:         case_stmt = text =~ /^-\s*case\b/
242:         keyword = mid_block_keyword?(text)
243:         block = block_opened? && !keyword
244: 
245:         # It's important to preserve tabulation modification for keywords
246:         # that involve choosing between posible blocks of code.
247:         if %w[else elsif when].include?(keyword)
248:           # @to_close_stack may not have a :script on top
249:           # when the preceding "- if" has nothing nested
250:           if @to_close_stack.last && @to_close_stack.last.first == :script
251:             @dont_indent_next_line, @dont_tab_up_next_text = @to_close_stack.last[1..2]
252:           else
253:             push_and_tabulate([:script, @dont_indent_next_line, @dont_tab_up_next_text])
254:           end
255: 
256:           # when is unusual in that either it will be indented twice,
257:           # or the case won't have created its own indentation
258:           if keyword == "when"
259:             push_and_tabulate([:script, @dont_indent_next_line, @dont_tab_up_next_text, false])
260:           end
261:         elsif block || case_stmt
262:           push_and_tabulate([:script, @dont_indent_next_line, @dont_tab_up_next_text])
263:         elsif block && case_stmt
264:           push_and_tabulate([:script, @dont_indent_next_line, @dont_tab_up_next_text])
265:         end
266:       when FILTER; start_filtered(text[1..-1].downcase)
267:       when DOCTYPE
268:         return render_doctype(text) if text[0...3] == '!!!'
269:         return push_plain(text[3..-1].strip, :escape_html => false) if text[1..2] == "=="
270:         return push_script(text[2..-1].strip, :escape_html => false) if text[1] == SCRIPT
271:         return push_flat_script(text[2..-1].strip, :escape_html => false) if text[1] == FLAT_SCRIPT
272:         return push_plain(text[1..-1].strip, :escape_html => false) if text[1] == ?\s
273:         push_plain text
274:       when ESCAPE; push_plain text[1..-1]
275:       else push_plain text
276:       end
277:     end

Pushes value onto `@to_close_stack` and increases `@template_tabs`.

[Source]

      # File lib/haml/precompiler.rb, line 1049
1049:     def push_and_tabulate(value)
1050:       @to_close_stack.push(value)
1051:       @template_tabs += 1
1052:     end

Adds text to `@buffer` while flattening text.

[Source]

     # File lib/haml/precompiler.rb, line 373
373:     def push_flat(line)
374:       text = line.full.dup
375:       text = "" unless text.gsub!(/^#{@flat_spaces}/, '')
376:       @filter_buffer << "#{text}\n"
377:     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 419
419:     def push_flat_script(text, options = {})
420:       flush_merged_text
421: 
422:       raise SyntaxError.new("There's no Ruby code for ~ to evaluate.") if text.empty?
423:       push_script(text, options.merge(:preserve_script => true))
424:     end

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

[Source]

     # File lib/haml/precompiler.rb, line 304
304:     def push_merged_text(text, tab_change = 0, indent = true)
305:       text = !indent || @dont_indent_next_line || @options[:ugly] ? text : "#{'  ' * @output_tabs}#{text}"
306:       @to_merge << [:text, text, tab_change]
307:       @dont_indent_next_line = false
308:     end

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

[Source]

     # File lib/haml/precompiler.rb, line 357
357:     def push_plain(text, options = {})
358:       if block_opened?
359:         raise SyntaxError.new("Illegal nesting: nesting within plain text is illegal.", @next_line.index)
360:       end
361: 
362:       if contains_interpolation?(text)
363:         options[:escape_html] = self.options[:escape_html] if options[:escape_html].nil?
364:         push_script(
365:           unescape_interpolation(text, :escape_html => options[:escape_html]),
366:           :escape_html => false)
367:       else
368:         push_text text
369:       end
370:     end

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

If `opts[: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 384
384:     def push_script(text, opts = {})
385:       raise SyntaxError.new("There's no Ruby code for = to evaluate.") if text.empty?
386:       text = handle_ruby_multiline(text)
387:       return if options[:suppress_eval]
388:       opts[:escape_html] = options[:escape_html] if opts[:escape_html].nil?
389: 
390:       args = %w[preserve_script in_tag preserve_tag escape_html nuke_inner_whitespace]
391:       args.map! {|name| opts[name.to_sym]}
392:       args << !block_opened? << @options[:ugly]
393: 
394:       no_format = @options[:ugly] &&
395:         !(opts[:preserve_script] || opts[:preserve_tag] || opts[:escape_html])
396:       output_expr = "(#{text}\n)"
397:       static_method = "_hamlout.#{static_method_name(:format_script, *args)}"
398: 
399:       # Prerender tabulation unless we're in a tag
400:       push_merged_text '' unless opts[:in_tag]
401: 
402:       unless block_opened?
403:         @to_merge << [:script, no_format ? "#{text}\n" : "#{static_method}(#{output_expr});"]
404:         concat_merged_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace]
405:         @newlines -= 1
406:         return
407:       end
408: 
409:       flush_merged_text
410: 
411:       push_silent "haml_temp = #{text}"
412:       newline_now
413:       push_and_tabulate([:loud, "_hamlout.buffer << #{no_format ? "haml_temp.to_s;" : "#{static_method}(haml_temp);"}",
414:         !(opts[:in_tag] || opts[:nuke_inner_whitespace] || @options[:ugly])])
415:     end

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

[Source]

     # File lib/haml/precompiler.rb, line 296
296:     def push_silent(text, can_suppress = false)
297:       flush_merged_text
298:       return if can_suppress && options[:suppress_eval]
299:       @precompiled << "#{text};"
300:     end

[Source]

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

[Source]

     # File lib/haml/precompiler.rb, line 929
929:     def raw_next_line
930:       text = @template.shift
931:       return unless text
932: 
933:       index = @template_index
934:       @template_index += 1
935: 
936:       return text, index
937:     end

Renders an XHTML comment.

[Source]

     # File lib/haml/precompiler.rb, line 845
845:     def render_comment(line)
846:       conditional, line = balance(line, ?[, ?]) if line[0] == ?[
847:       line.strip!
848:       conditional << ">" if conditional
849: 
850:       if block_opened? && !line.empty?
851:         raise SyntaxError.new('Illegal nesting: nesting within a tag that already has content is illegal.', @next_line.index)
852:       end
853: 
854:       open = "<!--#{conditional}"
855: 
856:       # Render it statically if possible
857:       unless line.empty?
858:         return push_text("#{open} #{line} #{conditional ? "<![endif]-->" : "-->"}")
859:       end
860: 
861:       push_text(open, 1)
862:       @output_tabs += 1
863:       push_and_tabulate([:comment, !conditional.nil?])
864:       unless line.empty?
865:         push_text(line)
866:         close
867:       end
868:     end

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

[Source]

     # File lib/haml/precompiler.rb, line 840
840:     def render_div(line)
841:       render_tag('%div' + line)
842:     end

Renders an XHTML doctype or XML shebang.

[Source]

     # File lib/haml/precompiler.rb, line 871
871:     def render_doctype(line)
872:       raise SyntaxError.new("Illegal nesting: nesting within a header command is illegal.", @next_line.index) if block_opened?
873:       doctype = text_for_doctype(line)
874:       push_text doctype if doctype
875:     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 706
706:     def render_tag(line)
707:       tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
708:         nuke_inner_whitespace, action, value, last_line = parse_tag(line)
709: 
710:       raise SyntaxError.new("Illegal element: classes and ids must have values.") if attributes =~ /[\.#](\.|#|\z)/
711: 
712:       # Get rid of whitespace outside of the tag if we need to
713:       rstrip_buffer! if nuke_outer_whitespace
714: 
715:       preserve_tag = options[:preserve].include?(tag_name)
716:       nuke_inner_whitespace ||= preserve_tag
717:       preserve_tag &&= !options[:ugly]
718: 
719:       escape_html = (action == '&' || (action != '!' && @options[:escape_html]))
720: 
721:       case action
722:       when '/'; self_closing = true
723:       when '~'; parse = preserve_script = true
724:       when '='
725:         parse = true
726:         if value[0] == ?=
727:           value = unescape_interpolation(value[1..-1].strip, :escape_html => escape_html)
728:           escape_html = false
729:         end
730:       when '&', '!'
731:         if value[0] == ?= || value[0] == ?~
732:           parse = true
733:           preserve_script = (value[0] == ?~)
734:           if value[1] == ?=
735:             value = unescape_interpolation(value[2..-1].strip, :escape_html => escape_html)
736:             escape_html = false
737:           else
738:             value = value[1..-1].strip
739:           end
740:         elsif contains_interpolation?(value)
741:           value = unescape_interpolation(value, :escape_html => escape_html)
742:           parse = true
743:           escape_html = false
744:         end
745:       else
746:         if contains_interpolation?(value)
747:           value = unescape_interpolation(value, :escape_html => escape_html)
748:           parse = true
749:           escape_html = false
750:         end
751:       end
752: 
753:       if parse && @options[:suppress_eval]
754:         parse = false
755:         value = ''
756:       end
757: 
758:       object_ref = "nil" if object_ref.nil? || @options[:suppress_eval]
759: 
760:       attributes = Precompiler.parse_class_and_id(attributes)
761:       attributes_hashes.map! do |syntax, attributes_hash|
762:         if syntax == :old
763:           static_attributes = parse_static_hash(attributes_hash)
764:           attributes_hash = nil if static_attributes || @options[:suppress_eval]
765:         else
766:           static_attributes, attributes_hash = attributes_hash
767:         end
768:         Buffer.merge_attrs(attributes, static_attributes) if static_attributes
769:         attributes_hash
770:       end.compact!
771: 
772:       raise SyntaxError.new("Illegal nesting: nesting within a self-closing tag is illegal.", @next_line.index) if block_opened? && self_closing
773:       raise SyntaxError.new("There's no Ruby code for #{action} to evaluate.", last_line - 1) if parse && value.empty?
774:       raise SyntaxError.new("Self-closing tags can't have content.", last_line - 1) if self_closing && !value.empty?
775: 
776:       if block_opened? && !value.empty? && !is_ruby_multiline?(value)
777:         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)
778:       end
779: 
780:       self_closing ||= !!(!block_opened? && value.empty? && @options[:autoclose].any? {|t| t === tag_name})
781:       value = nil if value.empty? && (block_opened? || self_closing)
782: 
783:       dont_indent_next_line =
784:         (nuke_outer_whitespace && !block_opened?) ||
785:         (nuke_inner_whitespace && block_opened?)
786: 
787:       # Check if we can render the tag directly to text and not process it in the buffer
788:       if object_ref == "nil" && attributes_hashes.empty? && !preserve_script
789:         tag_closed = !block_opened? && !self_closing && !parse
790: 
791:         open_tag  = prerender_tag(tag_name, self_closing, attributes)
792:         if tag_closed
793:           open_tag << "#{value}</#{tag_name}>"
794:           open_tag << "\n" unless nuke_outer_whitespace
795:         else
796:           open_tag << "\n" unless parse || nuke_inner_whitespace || (self_closing && nuke_outer_whitespace)
797:         end
798: 
799:         push_merged_text(open_tag, tag_closed || self_closing || nuke_inner_whitespace ? 0 : 1,
800:                          !nuke_outer_whitespace)
801: 
802:         @dont_indent_next_line = dont_indent_next_line
803:         return if tag_closed
804:       else
805:         flush_merged_text
806:         content = parse ? 'nil' : value.inspect
807:         if attributes_hashes.empty?
808:           attributes_hashes = ''
809:         elsif attributes_hashes.size == 1
810:           attributes_hashes = ", #{attributes_hashes.first}"
811:         else
812:           attributes_hashes = ", (#{attributes_hashes.join(").merge(")})"
813:         end
814: 
815:         args = [tag_name, self_closing, !block_opened?, preserve_tag, escape_html,
816:                 attributes, nuke_outer_whitespace, nuke_inner_whitespace
817:                ].map { |v| v.inspect }.join(', ')
818:         push_silent "_hamlout.open_tag(#{args}, #{object_ref}, #{content}#{attributes_hashes})"
819:         @dont_tab_up_next_text = @dont_indent_next_line = dont_indent_next_line
820:       end
821: 
822:       return if self_closing
823: 
824:       if value.nil?
825:         push_and_tabulate([:element, [tag_name, nuke_outer_whitespace, nuke_inner_whitespace]])
826:         @output_tabs += 1 unless nuke_inner_whitespace
827:         return
828:       end
829: 
830:       if parse
831:         push_script(value, :preserve_script => preserve_script, :in_tag => true,
832:           :preserve_tag => preserve_tag, :escape_html => escape_html,
833:           :nuke_inner_whitespace => nuke_inner_whitespace)
834:         concat_merged_text("</#{tag_name}>" + (nuke_outer_whitespace ? "" : "\n"))
835:       end
836:     end

[Source]

      # File lib/haml/precompiler.rb, line 1067
1067:     def resolve_newlines
1068:       return unless @newlines > 0
1069:       @to_merge << [:newlines, @newlines]
1070:       @newlines = 0
1071:     end

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

[Source]

      # File lib/haml/precompiler.rb, line 1075
1075:     def rstrip_buffer!(index = -1)
1076:       last = @to_merge[index]
1077:       if last.nil?
1078:         push_silent("_hamlout.rstrip!", false)
1079:         @dont_tab_up_next_text = true
1080:         return
1081:       end
1082: 
1083:       case last.first
1084:       when :text
1085:         last[1].rstrip!
1086:         if last[1].empty?
1087:           @to_merge.slice! index
1088:           rstrip_buffer! index
1089:         end
1090:       when :script
1091:         last[1].gsub!(/\(haml_temp, (.*?)\);$/, '(haml_temp.rstrip, \1);')
1092:         rstrip_buffer! index - 1
1093:       when :newlines
1094:         rstrip_buffer! index - 1
1095:       else
1096:         raise SyntaxError.new("[HAML BUG] Undefined entry in Haml::Precompiler@to_merge.")
1097:       end
1098:     end

Starts a filtered block.

[Source]

     # File lib/haml/precompiler.rb, line 917
917:     def start_filtered(name)
918:       raise Error.new("Invalid filter name \":#{name}\".") unless name =~ /^\w+$/
919:       raise Error.new("Filter \"#{name}\" is not defined.") unless filter = Filters.defined[name]
920: 
921:       push_and_tabulate([:filtered, filter])
922:       @flat = true
923:       @filter_buffer = String.new
924: 
925:       # If we don't know the indentation by now, it'll be set in Line#tabs
926:       @flat_spaces = @indentation * @template_tabs if @indentation
927:     end

[Source]

     # File lib/haml/precompiler.rb, line 426
426:     def start_haml_comment
427:       return unless block_opened?
428: 
429:       @haml_comment = true
430:       push_and_tabulate([:haml_comment])
431:     end

[Source]

     # File lib/haml/precompiler.rb, line 877
877:     def text_for_doctype(text)
878:       text = text[3..-1].lstrip.downcase
879:       if text.index("xml") == 0
880:         return nil if html?
881:         wrapper = @options[:attr_wrapper]
882:         return "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{text.split(' ')[1] || "utf-8"}#{wrapper} ?>"
883:       end
884: 
885:       if html5?
886:         '<!DOCTYPE html>'
887:       else
888:         version, type = text.scan(DOCTYPE_REGEX)[0]
889: 
890:         if xhtml?
891:           if version == "1.1"
892:             '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
893:           elsif version == "5"
894:             '<!DOCTYPE html>'
895:           else
896:             case type
897:             when "strict";   '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
898:             when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
899:             when "mobile";   '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
900:             when "rdfa";     '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">'
901:             when "basic";    '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
902:             else             '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
903:             end
904:           end
905: 
906:         elsif html4?
907:           case type
908:           when "strict";   '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
909:           when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
910:           else             '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
911:           end
912:         end
913:       end
914:     end

[Source]

     # File lib/haml/precompiler.rb, line 973
973:     def un_next_line(line)
974:       @template.unshift line
975:       @template_index -= 1
976:     end

[Source]

      # File lib/haml/precompiler.rb, line 1021
1021:     def unescape_interpolation(str, opts = {})
1022:       res = ''
1023:       rest = Haml::Shared.handle_interpolation str.dump do |scan|
1024:         escapes = (scan[2].size - 1) / 2
1025:         res << scan.matched[0...-3 - escapes]
1026:         if escapes % 2 == 1
1027:           res << '#{'
1028:         else
1029:           content = eval('"' + balance(scan, ?{, ?}, 1)[0][0...-1] + '"')
1030:           content = "Haml::Helpers.html_escape(#{content})" if opts[:escape_html]
1031:           res << '#{' + content + "}"# Use eval to get rid of string escapes
1032:         end
1033:       end
1034:       res + rest
1035:     end

[Validate]