1
0
Fork 0
mirror of https://github.com/haml/haml.git synced 2022-11-09 12:33:31 -05:00

No more line number mungeing - @precompiled line numbers now sync up with template line numbers.

git-svn-id: svn://hamptoncatlin.com/haml/trunk@680 7063305b-7217-0410-af8c-cdc13e5119b9
This commit is contained in:
nex3 2007-11-26 03:26:16 +00:00
parent 890521383d
commit 34619eb403
5 changed files with 51 additions and 151 deletions

View file

@ -40,7 +40,8 @@ module Haml
'preserve' => Haml::Filters::Preserve, 'preserve' => Haml::Filters::Preserve,
'redcloth' => Haml::Filters::RedCloth, 'redcloth' => Haml::Filters::RedCloth,
'textile' => Haml::Filters::Textile, 'textile' => Haml::Filters::Textile,
'markdown' => Haml::Filters::Markdown } 'markdown' => Haml::Filters::Markdown },
:filename => '(haml)'
} }
@options.rec_merge! options @options.rec_merge! options
@ -68,9 +69,9 @@ END
@flat_spaces = -1 @flat_spaces = -1
precompile precompile
rescue Haml::Error => e rescue Haml::Error
e.add_backtrace_entry(@index, @options[:filename]) $!.backtrace.unshift "#{@options[:filename]}:#{@index}"
raise e raise
end end
# Processes the template and returns the result as a string. # Processes the template and returns the result as a string.
@ -129,11 +130,7 @@ END
@haml_is_haml = true @haml_is_haml = true
end end
begin eval(@precompiled, scope, @options[:filename], 0)
eval(@precompiled, scope, '(haml-eval)')
rescue Exception => e
raise Engine.add_exception_info(e, @precompiled, @options[:filename])
end
# Get rid of the current buffer # Get rid of the current buffer
scope_object.instance_eval do scope_object.instance_eval do
@ -174,12 +171,8 @@ END
scope = scope_object.instance_eval{binding} scope = scope_object.instance_eval{binding}
end end
begin
eval("Proc.new { |*_haml_locals| _haml_locals = _haml_locals[0] || {};" + eval("Proc.new { |*_haml_locals| _haml_locals = _haml_locals[0] || {};" +
precompiled_with_ambles(local_names) + "}\n", scope, '(haml-eval)') precompiled_with_ambles(local_names) + "}\n", scope, @options[:filename], 0)
rescue Exception => e
raise Haml::Engine.add_exception_info(e, @precompiled, @options[:filename])
end
end end
# Defines a method on +object+ # Defines a method on +object+
@ -220,11 +213,8 @@ END
def def_method(object, name, *local_names) def def_method(object, name, *local_names)
method = object.is_a?(Module) ? :module_eval : :instance_eval method = object.is_a?(Module) ? :module_eval : :instance_eval
begin object.send(method, "def #{name}(_haml_locals = {}); #{precompiled_with_ambles(local_names)}; end",
object.send(method, "def #{name}(_haml_locals = {}); #{precompiled_with_ambles(local_names)}; end", '(haml-eval)') @options[:filename], 0)
rescue Exception => e
raise Haml::Engine.add_exception_info(e, @precompiled, @options[:filename])
end
end end
private private
@ -235,21 +225,6 @@ END
eval(set_locals, scope) eval(set_locals, scope)
end end
def self.add_exception_info(e, precompiled, filename)
metaclass = class << e; self; end
metaclass.send(:include, Haml::Error)
eval_line = e.backtrace[0].scan(/:([0-9]*)/)[0][0].to_i
line_marker = precompiled.split("\n")[0...eval_line].grep(/#haml_lineno: [0-9]+/)[-1]
e.add_backtrace_entry(line_marker ? line_marker.scan(/[0-9]+/)[0].to_i : -1, filename)
# Format Ruby compiler errors nicely
message = e.message.scan(/compile error\n\(haml-eval\):[0-9]*: (.*)/)[0]
metaclass.send(:define_method, :message) { "compile error: #{message}" } if message
return e
end
# Returns a hash of options that Haml::Buffer cares about. # Returns a hash of options that Haml::Buffer cares about.
# This should remain loadable form #inspect. # This should remain loadable form #inspect.
def options_for_buffer def options_for_buffer

View file

@ -1,43 +1,13 @@
module Haml module Haml
# The abstract type of exception raised by Haml code. # The abstract type of exception raised by Haml code.
# Haml::SyntaxError includes this module, class Error < StandardError; end
# as do all exceptions raised by Ruby code within Haml.
#
# Haml::Error encapsulates information about the exception,
# such as the line of the Haml template it was raised on
# and the Haml file that was being parsed (if applicable).
# It also provides a handy way to rescue only exceptions raised
# because of a faulty template.
module Error
# The line of the Haml template on which the exception was thrown.
attr_reader :haml_line
# The name of the file that was being parsed when the exception was raised.
# This will be nil unless Haml is being used as an ActionView plugin.
attr_reader :haml_filename
# Adds a properly formatted entry to the exception's backtrace.
# +lineno+ should be the line on which the error occurred.
# +filename+ should be the file in which the error occurred,
# if applicable (defaults to "(haml)").
def add_backtrace_entry(lineno, filename = nil) # :nodoc:
@haml_line = lineno
@haml_filename = filename
self.backtrace ||= []
self.backtrace.unshift "#{filename || '(haml)'}:#{lineno}"
end
end
# SyntaxError is the type of exception raised when Haml encounters an # SyntaxError is the type of exception raised when Haml encounters an
# ill-formatted document. # ill-formatted document.
# It's not particularly interesting, except in that it includes Haml::Error. # It's not particularly interesting, except in that it includes Haml::Error.
class SyntaxError < StandardError class SyntaxError < Haml::Error; end
include Haml::Error
end
# HamlError is the type of exception raised when Haml encounters an error # HamlError is the type of exception raised when Haml encounters an error
# not of a syntactical nature, such as an undefined Filter. # not of a syntactical nature, such as an undefined Filter.
class HamlError < StandardError class HamlError < Haml::Error; end
include Haml::Error
end
end end

View file

@ -98,12 +98,8 @@ extend Haml::Helpers
@haml_is_haml = true @haml_is_haml = true
_hamlout = @haml_stack[-1] _hamlout = @haml_stack[-1]
_erbout = _hamlout.buffer _erbout = _hamlout.buffer
begin
END END
postamble = <<END.gsub("\n", ";") postamble = <<END.gsub("\n", ";")
rescue Exception => e
raise Haml::Engine.add_exception_info(e, #{@precompiled.inspect}, #{@options[:filename].inspect})
end
@haml_is_haml = false @haml_is_haml = false
_hamlout.buffer _hamlout.buffer
END END
@ -132,10 +128,15 @@ END
if line.text.empty? if line.text.empty?
process_indent(old_line) unless !flat? || old_line.text.empty? process_indent(old_line) unless !flat? || old_line.text.empty?
next unless flat?
unless flat?
newline
next
end
push_flat(old_line) push_flat(old_line)
old_line.text, old_line.unstripped, old_line.spaces = '', '', 0 old_line.text, old_line.unstripped, old_line.spaces = '', '', 0
newline
next next
end end
@ -143,6 +144,7 @@ END
if old_line.text.nil? || suppress_render if old_line.text.nil? || suppress_render
old_line = line old_line = line
newline
next next
end end
@ -151,6 +153,7 @@ END
if flat? if flat?
push_flat(old_line) push_flat(old_line)
old_line = line old_line = line
newline
next next
end end
@ -166,6 +169,7 @@ END
raise SyntaxError.new("Illegal Indentation: Indenting more than once per line is illegal.") raise SyntaxError.new("Illegal Indentation: Indenting more than once per line is illegal.")
end end
old_line = line old_line = line
newline
end end
# Close all the open tags # Close all the open tags
@ -186,8 +190,8 @@ END
# This method doesn't return anything; it simply processes the line and # This method doesn't return anything; it simply processes the line and
# adds the appropriate code to <tt>@precompiled</tt>. # adds the appropriate code to <tt>@precompiled</tt>.
def process_line(text, index, block_opened) def process_line(text, index, block_opened)
@index = index + 1
@block_opened = block_opened @block_opened = block_opened
@index = index + 1
case text[0] case text[0]
when DIV_CLASS, DIV_ID: render_div(text) when DIV_CLASS, DIV_ID: render_div(text)
@ -200,9 +204,9 @@ END
when SILENT_SCRIPT when SILENT_SCRIPT
return start_haml_comment if text[1] == SILENT_COMMENT return start_haml_comment if text[1] == SILENT_COMMENT
mbk = mid_block_keyword?(text) push_silent(text[1..-1], true)
push_silent(text[1..-1], !mbk, true) newline true
if (@block_opened && !mbk) || text[1..-1].split(' ', 2)[0] == "case" if (@block_opened && !mid_block_keyword?(text)) || text[1..-1].split(' ', 2)[0] == "case"
push_and_tabulate([:script]) push_and_tabulate([:script])
end end
when FILTER: start_filtered(text[1..-1].downcase) when FILTER: start_filtered(text[1..-1].downcase)
@ -257,12 +261,10 @@ END
# Evaluates <tt>text</tt> in the context of the scope object, but # Evaluates <tt>text</tt> in the context of the scope object, but
# does not output the result. # does not output the result.
def push_silent(text, add_index = false, can_suppress = false) def push_silent(text, can_suppress = false)
flush_merged_text flush_merged_text
return if can_suppress && options[:suppress_eval] return if can_suppress && options[:suppress_eval]
@precompiled << "#{text};"
@precompiled << "#haml_lineno: #{@index}\n" if add_index
@precompiled << "#{text}\n"
end end
# Adds <tt>text</tt> to <tt>@buffer</tt> with appropriate tabulation # Adds <tt>text</tt> to <tt>@buffer</tt> with appropriate tabulation
@ -282,7 +284,7 @@ END
@precompiled << "_hamlout.push_text(#{@merged_text.dump}" @precompiled << "_hamlout.push_text(#{@merged_text.dump}"
@precompiled << ", #{@tab_change}" if @tab_change != 0 || @try_one_liner @precompiled << ", #{@tab_change}" if @tab_change != 0 || @try_one_liner
@precompiled << ")\n" @precompiled << ");"
@merged_text = '' @merged_text = ''
@tab_change = 0 @tab_change = 0
@try_one_liner = false @try_one_liner = false
@ -311,8 +313,9 @@ END
flush_merged_text flush_merged_text
return if options[:suppress_eval] return if options[:suppress_eval]
push_silent("haml_temp = #{text}", true) push_silent "haml_temp = #{text}"
out = "haml_temp = _hamlout.push_script(haml_temp, #{flattened.inspect}, #{close_tag.inspect})\n" newline true
out = "haml_temp = _hamlout.push_script(haml_temp, #{flattened.inspect}, #{close_tag.inspect});"
if @block_opened if @block_opened
push_and_tabulate([:loud, out]) push_and_tabulate([:loud, out])
else else
@ -359,7 +362,7 @@ END
# Closes a Ruby block. # Closes a Ruby block.
def close_block def close_block
push_silent "end", false, true push_silent "end", true
@template_tabs -= 1 @template_tabs -= 1
end end
@ -373,7 +376,7 @@ END
# Closes a loud Ruby block. # Closes a loud Ruby block.
def close_loud(command) def close_loud(command)
push_silent 'end', false, true push_silent 'end', true
@precompiled << command @precompiled << command
@template_tabs -= 1 @template_tabs -= 1
end end
@ -384,7 +387,7 @@ END
filtered = filter.new(@filter_buffer).render filtered = filter.new(@filter_buffer).render
if filter == Haml::Filters::Preserve if filter == Haml::Filters::Preserve
push_silent("_hamlout.buffer << #{filtered.dump} << \"\\n\"\n") push_silent("_hamlout.buffer << #{filtered.dump} << \"\\n\";")
else else
push_text(filtered.rstrip.gsub("\n", "\n#{' ' * @output_tabs}")) push_text(filtered.rstrip.gsub("\n", "\n#{' ' * @output_tabs}"))
end end
@ -520,7 +523,7 @@ END
else else
flush_merged_text flush_merged_text
content = value.empty? || parse ? 'nil' : value.dump content = value.empty? || parse ? 'nil' : value.dump
push_silent "_hamlout.open_tag(#{tag_name.inspect}, #{atomic.inspect}, #{(!value.empty?).inspect}, #{attributes.inspect}, #{object_ref}, #{content}, #{attributes_hash[1...-1]})", true push_silent "_hamlout.open_tag(#{tag_name.inspect}, #{atomic.inspect}, #{(!value.empty?).inspect}, #{attributes.inspect}, #{object_ref}, #{content}, #{attributes_hash[1...-1]})"
end end
return if atomic return if atomic
@ -641,7 +644,6 @@ END
spaces = line.index(/[^ ]/) spaces = line.index(/[^ ]/)
if line[spaces] == ?\t if line[spaces] == ?\t
return nil if line.strip.empty? return nil if line.strip.empty?
raise SyntaxError.new("Illegal Indentation: Only two space characters are allowed as tabulation.") raise SyntaxError.new("Illegal Indentation: Only two space characters are allowed as tabulation.")
end end
[spaces, spaces/2] [spaces, spaces/2]
@ -657,5 +659,11 @@ END
def flat? def flat?
@flat_spaces != -1 @flat_spaces != -1
end end
def newline(skip_next = false)
return @skip_next_newline = false if @skip_next_newline
@skip_next_newline = true if skip_next
@precompiled << "\n"
end
end end
end end

View file

@ -187,82 +187,29 @@ class EngineTest < Test::Unit::TestCase
render("a\nb\n!!!\n c\nd") render("a\nb\n!!!\n c\nd")
rescue Haml::SyntaxError => e rescue Haml::SyntaxError => e
assert_equal(e.message, "Illegal Nesting: Nesting within a header command is illegal.") assert_equal(e.message, "Illegal Nesting: Nesting within a header command is illegal.")
assert_equal(3, e.haml_line) assert_equal("(haml):3", e.backtrace[0])
assert_equal(nil, e.haml_filename)
assert_equal('(haml):3', e.backtrace[0])
rescue Exception => e rescue Exception => e
assert(false, '"a\nb\n!!!\n c\nd" doesn\'t produce a Haml::SyntaxError') assert(false, '"a\nb\n!!!\n c\nd" doesn\'t produce a Haml::SyntaxError')
else else
assert(false, '"a\nb\n!!!\n c\nd" doesn\'t produce an exception') assert(false, '"a\nb\n!!!\n c\nd" doesn\'t produce an exception')
end end
def test_exception_type def test_exception
render("%p hi\n= undefined\n= 12") render("%p\n hi\n %a= undefined\n= 12")
rescue Exception => e rescue Exception => e
assert(e.is_a?(Haml::Error)) assert_match("(haml):3", e.backtrace[0])
assert_equal(2, e.haml_line)
assert_equal(nil, e.haml_filename)
assert_equal('(haml):2', e.backtrace[0])
else
# Test failed... should have raised an exception
assert(false)
end
def test_def_method_exception_type
o = Object.new
Haml::Engine.new("%p hi\n= undefined\n= 12").def_method(o, :render)
o.render
rescue Exception => e
assert(e.is_a?(Haml::Error))
assert_equal(2, e.haml_line)
assert_equal(nil, e.haml_filename)
assert_equal('(haml):2', e.backtrace[0])
else
# Test failed... should have raised an exception
assert(false)
end
def test_render_proc_exception_type
Haml::Engine.new("%p hi\n= undefined\n= 12").render_proc.call
rescue Exception => e
assert(e.is_a?(Haml::Error))
assert_equal(2, e.haml_line)
assert_equal(nil, e.haml_filename)
assert_equal('(haml):2', e.backtrace[0])
else else
# Test failed... should have raised an exception # Test failed... should have raised an exception
assert(false) assert(false)
end end
def test_compile_error def test_compile_error
render("a\nb\n- fee do\nc") render("a\nb\n- fee)\nc")
rescue Exception => e rescue Exception => e
assert_match(/^compile error: syntax error/, e.message) assert_match(/^compile error\n\(haml\):3: syntax error/i, e.message)
assert_equal(3, e.haml_line)
else else
assert(false, assert(false,
'"a\nb\n- fee do\nc" doesn\'t produce an exception!') '"a\nb\n- fee)\nc" doesn\'t produce an exception!')
end
def test_def_method_compile_error
o = Object.new
Haml::Engine.new("a\nb\n- fee do\nc").def_method(o, :render)
rescue Exception => e
assert_match(/^compile error: syntax error/, e.message)
assert_equal(3, e.haml_line)
else
assert(false,
'"a\nb\n- fee do\nc" doesn\'t produce an exception!')
end
def test_render_proc_compile_error
Haml::Engine.new("a\nb\n- fee do\nc").render_proc
rescue Exception => e
assert_match(/^compile error: syntax error/, e.message)
assert_equal(3, e.haml_line)
else
assert(false,
'"a\nb\n- fee do\nc" doesn\'t produce an exception!')
end end
def test_unbalanced_brackets def test_unbalanced_brackets

View file

@ -145,7 +145,7 @@ class TemplateTest < Test::Unit::TestCase
render("- raise 'oops!'") render("- raise 'oops!'")
rescue Exception => e rescue Exception => e
assert_equal("oops!", e.message) assert_equal("oops!", e.message)
assert_equal("(haml):1", e.backtrace[0]) assert_match(/^\(haml\):1/, e.backtrace[0])
else else
assert false assert false
end end
@ -165,7 +165,7 @@ END
begin begin
render(template.chomp) render(template.chomp)
rescue Exception => e rescue Exception => e
assert_equal("(haml):5", e.backtrace[0]) assert_match(/^\(haml\):5/, e.backtrace[0])
else else
assert false assert false
end end