diff --git a/lib/haml/engine.rb b/lib/haml/engine.rb
index 05975743..c7b7362f 100644
--- a/lib/haml/engine.rb
+++ b/lib/haml/engine.rb
@@ -17,128 +17,172 @@ module Haml #:nodoc:
options.each { |k,v| eval("@#{k} = v") }
@template = template #String
- @result, @to_close_queue = String.new, []
+ @result, @precompiled, @to_close_queue = String.new, String.new, []
+ @scope_object = Object.new if @scope_object.nil?
end
def to_html
# Process each line of the template
@template.each_with_index do |line, index|
count, line = count_soft_tabs(line)
- surpress_render, line, count = handle_multiline(count, line)
+ suppress_render, line, count = handle_multiline(count, line)
- if !surpress_render && count && line
+ if !suppress_render && count && line
count, line = process_line(count, line)
end
end
-
+
+ # Make sure an ending multiline gets closed
+ handle_multiline(0, nil)
+
# Close all the open tags
@to_close_queue.length.times { close_tag }
-
+
+ # Compile the @precompiled buffer
+ compile
+
# Return the result string
@result
end
def process_line(count, line)
if line.strip[0, 3] == '!!!'
- @result << %|\n|
+ push_text %||
else
if count <= @to_close_queue.size && @to_close_queue.size > 0
(@to_close_queue.size - count).times { close_tag }
end
-
- case line[0..0]
- when '.', '#'
- render_div(line)
- when '%'
- render_tag(line)
- when '/'
- render_comment(line)
- when '='
- add template_eval(line[1, line.length]).to_s
- when '~'
- add find_and_flatten(template_eval(line[1, line.length])).to_s
- else
- add line.strip
+ if line.length > 0
+ case line[0].chr
+ when '.', '#'
+ render_div(line)
+ when '%'
+ render_tag(line)
+ when '/'
+ render_comment(line)
+ when '='
+ push_script(line[1, line.length])
+ when '~'
+ push_script(line[1, line.length], true)
+ else
+ push_text line.strip
+ end
end
end
- return count, line
end
def handle_multiline(count, line)
# Multilines are denoting by ending with a `|` (124)
- if (line[-1] == MULTILINE_CHAR_VALUE) && @multiline_buffer
+ if line && (line[-1] == MULTILINE_CHAR_VALUE) && @multiline_buffer
# A multiline string is active, and is being continued
@multiline_buffer += line[0...-1]
- supress_render = true
- elsif (line[-1] == MULTILINE_CHAR_VALUE) && (MULTILINE_STARTERS.include? line[0])
+ suppress_render = true
+ elsif line && (line[-1] == MULTILINE_CHAR_VALUE) && (MULTILINE_STARTERS.include? line[0])
# A multiline string has just been activated, start adding the lines
@multiline_buffer = line[0...-1]
@multiline_count = count
- supress_render = true
+ suppress_render = true
elsif @multiline_buffer
# A multiline string has just ended, make line into the result
process_line(@multiline_count, @multiline_buffer)
@multiline_buffer = nil
- supress_render = false
+ suppress_render = false
end
-
- return supress_render, line, count
+
+ return suppress_render, line, count
end
- def add(line)
- return if line.nil?
- line.to_s.each_line do |me|
- @result << tabs(@to_close_queue.size) << me.chomp << "\n"
+ def compile
+ # Set the local variables pointing to the buffer
+ result = @result
+ @scope_object.instance_eval do
+ @haml_stack =|| Array.new
+ @haml_stack.push(result)
+ self.class.instance_eval { include Haml::Helpers }
+ end
+
+ # Evaluate the buffer in the context of the scope object
+ # This automatically dumps the result into @result
+ @scope_object.instance_eval @precompiled
+
+ # Get rid of the current buffer
+ @scope_object.instance_eval do
+ @haml_stack.pop
+ end
+ end
+
+ def push_visible(text)
+ @precompiled << "@haml_stack[-1] << #{tabs(@to_close_queue.size).dump} << #{text}\n"
+ end
+
+ def push_silent(text)
+ @precompiled << "#{text}\n"
+ end
+
+ def push_text(text)
+ push_visible("#{text.dump} << \"\\n\"")
+ end
+
+ def push_script(text, flattened = false)
+ unless @suppress_eval
+ push_silent("haml_temp = #{text}")
+ if flattened
+ push_silent("haml_temp = find_and_flatten(haml_temp)")
+ end
+ push_visible("#{wrap_script("haml_temp", @to_close_queue.size)} << \"\\n\"")
end
end
def build_attributes(attributes = {})
- result = attributes.collect { |a,v|
+ result = attributes.collect do |a,v|
unless v.nil?
first_quote_type = v.to_s.scan(/['"]/).first
quote_type = (first_quote_type == "'") ? '"' : "'"
"#{a.to_s}=#{quote_type}#{v.to_s}#{quote_type}"
end
- }
+ end
result = result.compact.join(' ')
(attributes.empty? ? String.new : String.new(' ')) + result
end
def open_tag(name, attributes = {})
- add "<#{name.to_s}#{build_attributes(attributes)}>"
+ push_text "<#{name.to_s}#{build_attributes(attributes)}>"
@to_close_queue.push name
end
def close_tag
- add "#{@to_close_queue.pop}>"
+ push_text "#{@to_close_queue.pop}>"
end
def one_line_tag(name, value, attributes = {})
- add "<#{name.to_s}#{build_attributes(attributes)}>#{value}#{name.to_s}>"
+ push_text "<#{name.to_s}#{build_attributes(attributes)}>#{value}#{name.to_s}>"
end
def one_liner?(value)
value.length <= ONE_LINER_LENGTH && value.scan(/\n/).empty?
end
-
- def print_tag(name, value, attributes = {})
+
+ def push_tag(name, value, attributes = {}, parse = false, flattened = false)
unless value.empty?
- if one_liner? value
+ if !parse && one_liner?(value)
one_line_tag(name, value, attributes)
else
open_tag(name, attributes)
- add value
+ if parse
+ push_script(value, flattened)
+ else
+ push_text(value)
+ end
close_tag
end
else
open_tag(name, attributes)
- add value
end
end
# Creates single line tags, i.e.
Holy cow multiline tags! A pipe (|) even! PipesIgnored|PipesIgnored|PipesIgnored| 1|2|3
-