| Module | Haml::Precompiler |
| In: |
lib/haml/template/plugin.rb
lib/haml/precompiler.rb |
| 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 |
| push_silent | -> | push_silent_without_haml_block_deprecation |
This is a class method so it can be accessed from Buffer.
# File lib/haml/precompiler.rb, line 530
530: def self.build_attributes(is_html, attr_wrapper, attributes = {})
531: quote_escape = attr_wrapper == '"' ? """ : "'"
532: other_quote_char = attr_wrapper == '"' ? "'" : '"'
533:
534: if attributes['data'].is_a?(Hash)
535: attributes = attributes.dup
536: attributes =
537: Haml::Util.map_keys(attributes.delete('data')) {|name| "data-#{name}"}.merge(attributes)
538: end
539:
540: result = attributes.collect do |attr, value|
541: next if value.nil?
542:
543: value = filter_and_join(value, ' ') if attr == 'class'
544: value = filter_and_join(value, '_') if attr == 'id'
545:
546: if value == true
547: next " #{attr}" if is_html
548: next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
549: elsif value == false
550: next
551: end
552:
553: value = Haml::Helpers.preserve(Haml::Helpers.escape_once(value.to_s))
554: # We want to decide whether or not to escape quotes
555: value.gsub!('"', '"')
556: this_attr_wrapper = attr_wrapper
557: if value.include? attr_wrapper
558: if value.include? other_quote_char
559: value = value.gsub(attr_wrapper, quote_escape)
560: else
561: this_attr_wrapper = other_quote_char
562: end
563: end
564: " #{attr}=#{this_attr_wrapper}#{value}#{this_attr_wrapper}"
565: end
566: result.compact.sort.join
567: end
# File lib/haml/precompiler.rb, line 569
569: def self.filter_and_join(value, separator)
570: return "" if value == ""
571: value = [value] unless value.is_a?(Array)
572: value = value.flatten.collect {|item| item ? item.to_s : nil}.compact.join(separator)
573: return !value.empty? && value
574: 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.
# File lib/haml/precompiler.rb, line 497
497: def self.parse_class_and_id(list)
498: attributes = {}
499: list.scan(/([#.])([-:_a-zA-Z0-9]+)/) do |type, property|
500: case type
501: when '.'
502: if attributes['class']
503: attributes['class'] += " "
504: else
505: attributes['class'] = ""
506: end
507: attributes['class'] += property
508: when '#'; attributes['id'] = property
509: end
510: end
511: attributes
512: end
# File lib/haml/template/plugin.rb, line 59
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)
63: end
64:
65: push_silent_without_haml_block_deprecation("_hamlout.append_if_string= #{text}", can_suppress)
66: end
# File lib/haml/precompiler.rb, line 1045
1045: def balance(*args)
1046: res = Haml::Shared.balance(*args)
1047: return res if res
1048: raise SyntaxError.new("Unbalanced brackets.")
1049: end
# File lib/haml/precompiler.rb, line 1051
1051: def block_opened?
1052: !flat? && @next_line.tabs > @line.tabs
1053: end
Closes the most recent item in `@to_close_stack`.
# File lib/haml/precompiler.rb, line 435
435: def close
436: tag, *rest = @to_close_stack.pop
437: send("close_#{tag}", *rest)
438: end
Closes a comment.
# File lib/haml/precompiler.rb, line 459
459: def close_comment(has_conditional)
460: @output_tabs -= 1
461: @template_tabs -= 1
462: close_tag = has_conditional ? "<![endif]-->" : "-->"
463: push_text(close_tag, -1)
464: end
Puts a line in `@precompiled` that will add the closing tag of the most recently opened tag.
# File lib/haml/precompiler.rb, line 442
442: def close_element(value)
443: tag, nuke_outer_whitespace, nuke_inner_whitespace = value
444: @output_tabs -= 1 unless nuke_inner_whitespace
445: @template_tabs -= 1
446: rstrip_buffer! if nuke_inner_whitespace
447: push_merged_text("</#{tag}>" + (nuke_outer_whitespace ? "" : "\n"),
448: nuke_inner_whitespace ? 0 : -1, !nuke_inner_whitespace)
449: @dont_indent_next_line = nuke_outer_whitespace
450: end
Closes a filtered block.
# File lib/haml/precompiler.rb, line 475
475: def close_filtered(filter)
476: filter.internal_compile(self, @filter_buffer)
477: @flat = false
478: @flat_spaces = nil
479: @filter_buffer = nil
480: @template_tabs -= 1
481: end
# File lib/haml/precompiler.rb, line 483
483: def close_haml_comment
484: @haml_comment = false
485: @template_tabs -= 1
486: end
Closes a loud Ruby block.
# File lib/haml/precompiler.rb, line 467
467: def close_loud(command, add_newline, push_end = true)
468: push_silent('end', true) if push_end
469: @precompiled << command
470: @template_tabs -= 1
471: concat_merged_text("\n") if add_newline
472: end
# File lib/haml/precompiler.rb, line 488
488: def close_nil(*args)
489: @template_tabs -= 1
490: end
Closes a Ruby block.
# File lib/haml/precompiler.rb, line 453
453: def close_script(_1, _2, push_end = true)
454: push_silent("end", true) if push_end
455: @template_tabs -= 1
456: end
# File lib/haml/precompiler.rb, line 977
977: def closes_flat?(line)
978: line && !line.text.empty? && line.full !~ /^#{@flat_spaces}/
979: end
Concatenate `text` to `@buffer` without tabulation.
# File lib/haml/precompiler.rb, line 310
310: def concat_merged_text(text)
311: @to_merge << [:text, text, 0]
312: end
# File lib/haml/precompiler.rb, line 1025
1025: def contains_interpolation?(str)
1026: str.include?('#{')
1027: end
# File lib/haml/precompiler.rb, line 318
318: def flush_merged_text
319: return if @to_merge.empty?
320:
321: str = ""
322: mtabs = 0
323: newlines = 0
324: @to_merge.each do |type, val, tabs|
325: case type
326: when :text
327: str << inspect_obj(val)[1...-1]
328: mtabs += tabs
329: when :script
330: if mtabs != 0 && !@options[:ugly]
331: val = "_hamlout.adjust_tabs(#{mtabs}); " + val
332: end
333: str << "\#{#{"\n" * newlines}#{val}}"
334: mtabs = 0
335: newlines = 0
336: when :newlines
337: newlines += val
338: else
339: raise SyntaxError.new("[HAML BUG] Undefined entry in Haml::Precompiler@to_merge.")
340: end
341: end
342:
343: unless str.empty?
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: end
351: @precompiled << "\n" * newlines
352: @to_merge = []
353: @dont_tab_up_next_text = false
354: end
# File lib/haml/precompiler.rb, line 986
986: def handle_multiline(line)
987: return unless is_multiline?(line.text)
988: line.text.slice!(-1)
989: while new_line = raw_next_line.first
990: break if new_line == :eod
991: newline and next if new_line.strip.empty?
992: break unless is_multiline?(new_line.strip)
993: line.text << new_line.strip[0...-1]
994: newline
995: end
996: un_next_line new_line
997: resolve_newlines
998: end
# File lib/haml/precompiler.rb, line 1005
1005: def handle_ruby_multiline(text)
1006: text = text.rstrip
1007: return text unless is_ruby_multiline?(text)
1008: un_next_line @next_line.full
1009: begin
1010: new_line = raw_next_line.first
1011: break if new_line == :eod
1012: newline and next if new_line.strip.empty?
1013: text << " " << new_line.strip
1014: newline
1015: end while is_ruby_multiline?(new_line.strip)
1016: next_line
1017: resolve_newlines
1018: text
1019: end
Checks whether or not line is in a multiline sequence.
# File lib/haml/precompiler.rb, line 1001
1001: def is_multiline?(text)
1002: text && text.length > 1 && text[-1] == MULTILINE_CHAR_VALUE && text[-2] == ?\s
1003: end
# File lib/haml/precompiler.rb, line 1021
1021: def is_ruby_multiline?(text)
1022: text && text.length > 1 && text[-1] == ?, && text[-2] != ?? && text[-3..-2] != "?\\"
1023: end
# 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[#{inspect_obj(name.to_sym)}]"
124: str_local = "_haml_locals[#{inspect_obj(name.to_s)}]"
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.
# File lib/haml/precompiler.rb, line 289
289: def mid_block_keyword?(text)
290: text[MID_BLOCK_KEYWORD_REGEX, 1]
291: end
# File lib/haml/precompiler.rb, line 1070
1070: def newline_now
1071: @precompiled << "\n"
1072: @newlines -= 1
1073: end
# File lib/haml/precompiler.rb, line 947
947: def next_line
948: text, index = raw_next_line
949: return unless text
950:
951: # :eod is a special end-of-document marker
952: line =
953: if text == :eod
954: Line.new '-#', '-#', '-#', index, self, true
955: else
956: Line.new text.strip, text.lstrip.chomp, text, index, self, false
957: end
958:
959: # `flat?' here is a little outdated,
960: # so we have to manually check if either the previous or current line
961: # closes the flat block,
962: # as well as whether a new block is opened
963: @line.tabs if @line
964: unless (flat? && !closes_flat?(line) && !closes_flat?(@line)) ||
965: (@line && @line.text[0] == ?: && line.full =~ %r[^#{@line.full[/^\s+/]}\s])
966: if line.text.empty?
967: newline
968: return next_line
969: end
970:
971: handle_multiline(line)
972: end
973:
974: @next_line = line
975: end
# File lib/haml/precompiler.rb, line 678
678: def parse_new_attribute(scanner)
679: unless name = scanner.scan(/[-:\w]+/)
680: return if scanner.scan(/\)/)
681: return false
682: end
683:
684: scanner.scan(/\s*/)
685: return name, [:static, true] unless scanner.scan(/=/) #/end
686:
687: scanner.scan(/\s*/)
688: unless quote = scanner.scan(/["']/)
689: return false unless var = scanner.scan(/(@@?|\$)?\w+/)
690: return name, [:dynamic, var]
691: end
692:
693: re = /((?:\\.|\#(?!\{)|[^#{quote}\\#])*)(#{quote}|#\{)/
694: content = []
695: loop do
696: return false unless scanner.scan(re)
697: content << [:str, scanner[1].gsub(/\\(.)/, '\1')]
698: break if scanner[2] == quote
699: content << [:ruby, balance(scanner, ?{, ?}, 1).first[0...-1]]
700: end
701:
702: return name, [:static, content.first[1]] if content.size == 1
703: return name, [:dynamic,
704: '"' + content.map {|(t, v)| t == :str ? inspect_obj(v)[1...-1] : "\#{#{v}}"}.join + '"']
705: end
# File lib/haml/precompiler.rb, line 637
637: def parse_new_attributes(line)
638: line = line.dup
639: scanner = StringScanner.new(line)
640: last_line = @index
641: attributes = {}
642:
643: scanner.scan(/\(\s*/)
644: loop do
645: name, value = parse_new_attribute(scanner)
646: break if name.nil?
647:
648: if name == false
649: text = (Haml::Shared.balance(line, ?(, ?)) || [line]).first
650: raise Haml::SyntaxError.new("Invalid attribute list: #{text.inspect}.", last_line - 1)
651: end
652: attributes[name] = value
653: scanner.scan(/\s*/)
654:
655: if scanner.eos?
656: line << " " << @next_line.text
657: last_line += 1
658: next_line
659: scanner.scan(/\s*/)
660: end
661: end
662:
663: static_attributes = {}
664: dynamic_attributes = "{"
665: attributes.each do |name, (type, val)|
666: if type == :static
667: static_attributes[name] = val
668: else
669: dynamic_attributes << inspect_obj(name) << " => " << val << ","
670: end
671: end
672: dynamic_attributes << "}"
673: dynamic_attributes = nil if dynamic_attributes == "{}"
674:
675: return [static_attributes, dynamic_attributes], scanner.rest, last_line
676: end
# File lib/haml/precompiler.rb, line 616
616: def parse_old_attributes(line)
617: line = line.dup
618: last_line = @index
619:
620: begin
621: attributes_hash, rest = balance(line, ?{, ?})
622: rescue SyntaxError => e
623: if line.strip[-1] == ?, && e.message == "Unbalanced brackets."
624: line << "\n" << @next_line.text
625: last_line += 1
626: next_line
627: retry
628: end
629:
630: raise e
631: end
632:
633: attributes_hash = attributes_hash[1...-1] if attributes_hash
634: return attributes_hash, rest, last_line
635: end
# File lib/haml/precompiler.rb, line 514
514: def parse_static_hash(text)
515: attributes = {}
516: scanner = StringScanner.new(text)
517: scanner.scan(/\s+/)
518: until scanner.eos?
519: return unless key = scanner.scan(LITERAL_VALUE_REGEX)
520: return unless scanner.scan(/\s*=>\s*/)
521: return unless value = scanner.scan(LITERAL_VALUE_REGEX)
522: return unless scanner.scan(/\s*(?:,|$)\s*/)
523: attributes[eval(key).to_s] = eval(value).to_s
524: end
525: text.count("\n").times { newline }
526: attributes
527: end
Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value
# File lib/haml/precompiler.rb, line 582
582: def parse_tag(line)
583: raise SyntaxError.new("Invalid tag: \"#{line}\".") unless match = line.scan(/%([-:\w]+)([-:\w\.\#]*)(.*)/)[0]
584: tag_name, attributes, rest = match
585: new_attributes_hash = old_attributes_hash = last_line = object_ref = nil
586: attributes_hashes = {}
587: while rest
588: case rest[0]
589: when ?{
590: break if old_attributes_hash
591: old_attributes_hash, rest, last_line = parse_old_attributes(rest)
592: attributes_hashes[:old] = old_attributes_hash
593: when ?(
594: break if new_attributes_hash
595: new_attributes_hash, rest, last_line = parse_new_attributes(rest)
596: attributes_hashes[:new] = new_attributes_hash
597: when ?[
598: break if object_ref
599: object_ref, rest = balance(rest, ?[, ?])
600: else; break
601: end
602: end
603:
604: if rest
605: nuke_whitespace, action, value = rest.scan(/(<>|><|[><])?([=\/\~&!])?(.*)?/)[0]
606: nuke_whitespace ||= ''
607: nuke_outer_whitespace = nuke_whitespace.include? '>'
608: nuke_inner_whitespace = nuke_whitespace.include? '<'
609: end
610:
611: value = value.to_s.strip
612: [tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
613: nuke_inner_whitespace, action, value, last_line || @index]
614: end
# 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.
# 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
# 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
# File lib/haml/precompiler.rb, line 576
576: def prerender_tag(name, self_close, attributes)
577: attributes_string = Precompiler.build_attributes(html?, @options[:attr_wrapper], attributes)
578: "<#{name}#{attributes_string}#{self_close && xhtml? ? ' /' : ''}>"
579: end
Processes and deals with lowering indentation.
# 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`.
# 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: keyword = mid_block_keyword?(text)
242: block = block_opened? && !keyword
243:
244: # It's important to preserve tabulation modification for keywords
245: # that involve choosing between posible blocks of code.
246: if %w[else elsif when].include?(keyword)
247: # Whether a script block has already been opened immediately above this line
248: was_opened = @to_close_stack.last && @to_close_stack.last.first == :script
249: if was_opened
250: @dont_indent_next_line, @dont_tab_up_next_text = @to_close_stack.last[1..2]
251: end
252:
253: # when is unusual in that either it will be indented twice,
254: # or the case won't have created its own indentation.
255: # Also, if no block has been opened yet, we need to make sure we add an end
256: # once we de-indent.
257: if !was_opened || keyword == "when"
258: push_and_tabulate([
259: :script, @dont_indent_next_line, @dont_tab_up_next_text,
260: !was_opened])
261: end
262: elsif block || text =~ /^-\s*(case|if)\b/
263: push_and_tabulate([:script, @dont_indent_next_line, @dont_tab_up_next_text])
264: end
265: when FILTER; start_filtered(text[1..-1].downcase)
266: when DOCTYPE
267: return render_doctype(text) if text[0...3] == '!!!'
268: return push_plain(text[3..-1].strip, :escape_html => false) if text[1..2] == "=="
269: return push_script(text[2..-1].strip, :escape_html => false) if text[1] == SCRIPT
270: return push_flat_script(text[2..-1].strip, :escape_html => false) if text[1] == FLAT_SCRIPT
271: return push_plain(text[1..-1].strip, :escape_html => false) if text[1] == ?\s
272: push_plain text
273: when ESCAPE; push_plain text[1..-1]
274: else push_plain text
275: end
276: end
Pushes value onto `@to_close_stack` and increases `@template_tabs`.
# File lib/haml/precompiler.rb, line 1057
1057: def push_and_tabulate(value)
1058: @to_close_stack.push(value)
1059: @template_tabs += 1
1060: end
Adds text to `@buffer` while flattening text.
# File lib/haml/precompiler.rb, line 374
374: def push_flat(line)
375: text = line.full.dup
376: text = "" unless text.gsub!(/^#{@flat_spaces}/, '')
377: @filter_buffer << "#{text}\n"
378: end
Causes `text` to be evaluated, and Haml::Helpers#find_and_flatten to be run on it afterwards.
# File lib/haml/precompiler.rb, line 420
420: def push_flat_script(text, options = {})
421: flush_merged_text
422:
423: raise SyntaxError.new("There's no Ruby code for ~ to evaluate.") if text.empty?
424: push_script(text, options.merge(:preserve_script => true))
425: end
Adds `text` to `@buffer` with appropriate tabulation without parsing it.
# File lib/haml/precompiler.rb, line 303
303: def push_merged_text(text, tab_change = 0, indent = true)
304: text = !indent || @dont_indent_next_line || @options[:ugly] ? text : "#{' ' * @output_tabs}#{text}"
305: @to_merge << [:text, text, tab_change]
306: @dont_indent_next_line = false
307: end
Renders a block of text as plain text. Also checks for an illegally opened block.
# File lib/haml/precompiler.rb, line 358
358: def push_plain(text, options = {})
359: if block_opened?
360: raise SyntaxError.new("Illegal nesting: nesting within plain text is illegal.", @next_line.index)
361: end
362:
363: if contains_interpolation?(text)
364: options[:escape_html] = self.options[:escape_html] if options[:escape_html].nil?
365: push_script(
366: unescape_interpolation(text, :escape_html => options[:escape_html]),
367: :escape_html => false)
368: else
369: push_text text
370: end
371: 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`
# File lib/haml/precompiler.rb, line 385
385: def push_script(text, opts = {})
386: raise SyntaxError.new("There's no Ruby code for = to evaluate.") if text.empty?
387: text = handle_ruby_multiline(text)
388: return if options[:suppress_eval]
389: opts[:escape_html] = options[:escape_html] if opts[:escape_html].nil?
390:
391: args = %w[preserve_script in_tag preserve_tag escape_html nuke_inner_whitespace]
392: args.map! {|name| opts[name.to_sym]}
393: args << !block_opened? << @options[:ugly]
394:
395: no_format = @options[:ugly] &&
396: !(opts[:preserve_script] || opts[:preserve_tag] || opts[:escape_html])
397: output_expr = "(#{text}\n)"
398: static_method = "_hamlout.#{static_method_name(:format_script, *args)}"
399:
400: # Prerender tabulation unless we're in a tag
401: push_merged_text '' unless opts[:in_tag]
402:
403: unless block_opened?
404: @to_merge << [:script, no_format ? "#{text}\n" : "#{static_method}(#{output_expr});"]
405: concat_merged_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace]
406: @newlines -= 1
407: return
408: end
409:
410: flush_merged_text
411:
412: push_silent "haml_temp = #{text}"
413: newline_now
414: push_and_tabulate([:loud, "_hamlout.buffer << #{no_format ? "haml_temp.to_s;" : "#{static_method}(haml_temp);"}",
415: !(opts[:in_tag] || opts[:nuke_inner_whitespace] || @options[:ugly])])
416: end
Evaluates `text` in the context of the scope object, but does not output the result.
# File lib/haml/precompiler.rb, line 295
295: def push_silent(text, can_suppress = false)
296: flush_merged_text
297: return if can_suppress && options[:suppress_eval]
298: @precompiled << "#{text};"
299: end
# File lib/haml/precompiler.rb, line 314
314: def push_text(text, tab_change = 0)
315: push_merged_text("#{text}\n", tab_change)
316: end
# File lib/haml/precompiler.rb, line 937
937: def raw_next_line
938: text = @template.shift
939: return unless text
940:
941: index = @template_index
942: @template_index += 1
943:
944: return text, index
945: end
Renders an XHTML comment.
# File lib/haml/precompiler.rb, line 853
853: def render_comment(line)
854: conditional, line = balance(line, ?[, ?]) if line[0] == ?[
855: line.strip!
856: conditional << ">" if conditional
857:
858: if block_opened? && !line.empty?
859: raise SyntaxError.new('Illegal nesting: nesting within a tag that already has content is illegal.', @next_line.index)
860: end
861:
862: open = "<!--#{conditional}"
863:
864: # Render it statically if possible
865: unless line.empty?
866: return push_text("#{open} #{line} #{conditional ? "<![endif]-->" : "-->"}")
867: end
868:
869: push_text(open, 1)
870: @output_tabs += 1
871: push_and_tabulate([:comment, !conditional.nil?])
872: unless line.empty?
873: push_text(line)
874: close
875: end
876: end
Renders a line that creates an XHTML tag and has an implicit div because of `.` or `#`.
# File lib/haml/precompiler.rb, line 848
848: def render_div(line)
849: render_tag('%div' + line)
850: end
Renders an XHTML doctype or XML shebang.
# File lib/haml/precompiler.rb, line 879
879: def render_doctype(line)
880: raise SyntaxError.new("Illegal nesting: nesting within a header command is illegal.", @next_line.index) if block_opened?
881: doctype = text_for_doctype(line)
882: push_text doctype if doctype
883: end
Parses a line that will render as an XHTML tag, and adds the code that will render that tag to `@precompiled`.
# File lib/haml/precompiler.rb, line 709
709: def render_tag(line)
710: tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
711: nuke_inner_whitespace, action, value, last_line = parse_tag(line)
712:
713: raise SyntaxError.new("Illegal element: classes and ids must have values.") if attributes =~ /[\.#](\.|#|\z)/
714:
715: # Get rid of whitespace outside of the tag if we need to
716: rstrip_buffer! if nuke_outer_whitespace
717:
718: preserve_tag = options[:preserve].include?(tag_name)
719: nuke_inner_whitespace ||= preserve_tag
720: preserve_tag &&= !options[:ugly]
721:
722: escape_html = (action == '&' || (action != '!' && @options[:escape_html]))
723:
724: case action
725: when '/'; self_closing = true
726: when '~'; parse = preserve_script = true
727: when '='
728: parse = true
729: if value[0] == ?=
730: value = unescape_interpolation(value[1..-1].strip, :escape_html => escape_html)
731: escape_html = false
732: end
733: when '&', '!'
734: if value[0] == ?= || value[0] == ?~
735: parse = true
736: preserve_script = (value[0] == ?~)
737: if value[1] == ?=
738: value = unescape_interpolation(value[2..-1].strip, :escape_html => escape_html)
739: escape_html = false
740: else
741: value = value[1..-1].strip
742: end
743: elsif contains_interpolation?(value)
744: value = unescape_interpolation(value, :escape_html => escape_html)
745: parse = true
746: escape_html = false
747: end
748: else
749: if contains_interpolation?(value)
750: value = unescape_interpolation(value, :escape_html => escape_html)
751: parse = true
752: escape_html = false
753: end
754: end
755:
756: if parse && @options[:suppress_eval]
757: parse = false
758: value = ''
759: end
760:
761: object_ref = "nil" if object_ref.nil? || @options[:suppress_eval]
762:
763: attributes = Precompiler.parse_class_and_id(attributes)
764: attributes_list = []
765:
766: if attributes_hashes[:new]
767: static_attributes, attributes_hash = attributes_hashes[:new]
768: Buffer.merge_attrs(attributes, static_attributes) if static_attributes
769: attributes_list << attributes_hash
770: end
771:
772: if attributes_hashes[:old]
773: static_attributes = parse_static_hash(attributes_hashes[:old])
774: Buffer.merge_attrs(attributes, static_attributes) if static_attributes
775: attributes_list << attributes_hashes[:old] unless static_attributes || @options[:suppress_eval]
776: end
777:
778: attributes_list.compact!
779:
780: raise SyntaxError.new("Illegal nesting: nesting within a self-closing tag is illegal.", @next_line.index) if block_opened? && self_closing
781: raise SyntaxError.new("There's no Ruby code for #{action} to evaluate.", last_line - 1) if parse && value.empty?
782: raise SyntaxError.new("Self-closing tags can't have content.", last_line - 1) if self_closing && !value.empty?
783:
784: if block_opened? && !value.empty? && !is_ruby_multiline?(value)
785: 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)
786: end
787:
788: self_closing ||= !!(!block_opened? && value.empty? && @options[:autoclose].any? {|t| t === tag_name})
789: value = nil if value.empty? && (block_opened? || self_closing)
790:
791: dont_indent_next_line =
792: (nuke_outer_whitespace && !block_opened?) ||
793: (nuke_inner_whitespace && block_opened?)
794:
795: # Check if we can render the tag directly to text and not process it in the buffer
796: if object_ref == "nil" && attributes_list.empty? && !preserve_script
797: tag_closed = !block_opened? && !self_closing && !parse
798:
799: open_tag = prerender_tag(tag_name, self_closing, attributes)
800: if tag_closed
801: open_tag << "#{value}</#{tag_name}>"
802: open_tag << "\n" unless nuke_outer_whitespace
803: else
804: open_tag << "\n" unless parse || nuke_inner_whitespace || (self_closing && nuke_outer_whitespace)
805: end
806:
807: push_merged_text(open_tag, tag_closed || self_closing || nuke_inner_whitespace ? 0 : 1,
808: !nuke_outer_whitespace)
809:
810: @dont_indent_next_line = dont_indent_next_line
811: return if tag_closed
812: else
813: flush_merged_text
814: content = parse ? 'nil' : inspect_obj(value)
815: if attributes_list.empty?
816: attributes_list = ''
817: elsif attributes_list.size == 1
818: attributes_list = ", #{attributes_list.first}"
819: else
820: attributes_list = ", (#{attributes_list.join(").merge(")})"
821: end
822:
823: args = [tag_name, self_closing, !block_opened?, preserve_tag, escape_html,
824: attributes, nuke_outer_whitespace, nuke_inner_whitespace
825: ].map {|v| inspect_obj(v)}.join(', ')
826: push_silent "_hamlout.open_tag(#{args}, #{object_ref}, #{content}#{attributes_list})"
827: @dont_tab_up_next_text = @dont_indent_next_line = dont_indent_next_line
828: end
829:
830: return if self_closing
831:
832: if value.nil?
833: push_and_tabulate([:element, [tag_name, nuke_outer_whitespace, nuke_inner_whitespace]])
834: @output_tabs += 1 unless nuke_inner_whitespace
835: return
836: end
837:
838: if parse
839: push_script(value, :preserve_script => preserve_script, :in_tag => true,
840: :preserve_tag => preserve_tag, :escape_html => escape_html,
841: :nuke_inner_whitespace => nuke_inner_whitespace)
842: concat_merged_text("</#{tag_name}>" + (nuke_outer_whitespace ? "" : "\n"))
843: end
844: end
# File lib/haml/precompiler.rb, line 1075
1075: def resolve_newlines
1076: return unless @newlines > 0
1077: @to_merge << [:newlines, @newlines]
1078: @newlines = 0
1079: end
Get rid of and whitespace at the end of the buffer or the merged text
# File lib/haml/precompiler.rb, line 1083
1083: def rstrip_buffer!(index = -1)
1084: last = @to_merge[index]
1085: if last.nil?
1086: push_silent("_hamlout.rstrip!", false)
1087: @dont_tab_up_next_text = true
1088: return
1089: end
1090:
1091: case last.first
1092: when :text
1093: last[1].rstrip!
1094: if last[1].empty?
1095: @to_merge.slice! index
1096: rstrip_buffer! index
1097: end
1098: when :script
1099: last[1].gsub!(/\(haml_temp, (.*?)\);$/, '(haml_temp.rstrip, \1);')
1100: rstrip_buffer! index - 1
1101: when :newlines
1102: rstrip_buffer! index - 1
1103: else
1104: raise SyntaxError.new("[HAML BUG] Undefined entry in Haml::Precompiler@to_merge.")
1105: end
1106: end
Starts a filtered block.
# File lib/haml/precompiler.rb, line 925
925: def start_filtered(name)
926: raise Error.new("Invalid filter name \":#{name}\".") unless name =~ /^\w+$/
927: raise Error.new("Filter \"#{name}\" is not defined.") unless filter = Filters.defined[name]
928:
929: push_and_tabulate([:filtered, filter])
930: @flat = true
931: @filter_buffer = String.new
932:
933: # If we don't know the indentation by now, it'll be set in Line#tabs
934: @flat_spaces = @indentation * @template_tabs if @indentation
935: end
# File lib/haml/precompiler.rb, line 427
427: def start_haml_comment
428: return unless block_opened?
429:
430: @haml_comment = true
431: push_and_tabulate([:haml_comment])
432: end
# File lib/haml/precompiler.rb, line 885
885: def text_for_doctype(text)
886: text = text[3..-1].lstrip.downcase
887: if text.index("xml") == 0
888: return nil if html?
889: wrapper = @options[:attr_wrapper]
890: return "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{text.split(' ')[1] || "utf-8"}#{wrapper} ?>"
891: end
892:
893: if html5?
894: '<!DOCTYPE html>'
895: else
896: version, type = text.scan(DOCTYPE_REGEX)[0]
897:
898: if xhtml?
899: if version == "1.1"
900: '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
901: elsif version == "5"
902: '<!DOCTYPE html>'
903: else
904: case type
905: when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
906: when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
907: when "mobile"; '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
908: when "rdfa"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">'
909: when "basic"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
910: else '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
911: end
912: end
913:
914: elsif html4?
915: case type
916: when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
917: when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
918: else '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
919: end
920: end
921: end
922: end
# File lib/haml/precompiler.rb, line 981
981: def un_next_line(line)
982: @template.unshift line
983: @template_index -= 1
984: end
# File lib/haml/precompiler.rb, line 1029
1029: def unescape_interpolation(str, opts = {})
1030: res = ''
1031: rest = Haml::Shared.handle_interpolation inspect_obj(str) do |scan|
1032: escapes = (scan[2].size - 1) / 2
1033: res << scan.matched[0...-3 - escapes]
1034: if escapes % 2 == 1
1035: res << '#{'
1036: else
1037: content = eval('"' + balance(scan, ?{, ?}, 1)[0][0...-1] + '"')
1038: content = "Haml::Helpers.html_escape((#{content}))" if opts[:escape_html]
1039: res << '#{' + content + "}"# Use eval to get rid of string escapes
1040: end
1041: end
1042: res + rest
1043: end