1
0
Fork 0
mirror of https://github.com/sinatra/sinatra synced 2023-03-27 23:18:01 -04:00

template backtraces ftw [#198] [#51]

This commit is contained in:
S. Brent Faulkner 2009-04-06 00:01:39 -04:00 committed by Ryan Tomayko
parent 801163e9f3
commit cfdf97d495
8 changed files with 199 additions and 27 deletions

View file

@ -183,13 +183,14 @@ context "Haml" do
Sinatra.application = nil Sinatra.application = nil
end end
specify 'are empty be default' do specify 'default to filename and line of caller' do
get '/' do get '/' do
haml 'foo' haml 'foo'
end end
Haml::Engine.expects(:new).with('foo', {}).returns(stub(:render => 'foo')) Haml::Engine.expects(:new).with('foo', {:filename => __FILE__,
:line => (__LINE__-4)}).returns(stub(:render => 'foo'))
get_it '/' get_it '/'
should.be.ok should.be.ok
@ -202,7 +203,8 @@ context "Haml" do
haml 'foo', :options => {:format => :html4} haml 'foo', :options => {:format => :html4}
end end
Haml::Engine.expects(:new).with('foo', {:format => :html4}).returns(stub(:render => 'foo')) Haml::Engine.expects(:new).with('foo', {:filename => __FILE__,
:line => (__LINE__-4), :format => :html4}).returns(stub(:render => 'foo'))
get_it '/' get_it '/'
should.be.ok should.be.ok
@ -220,7 +222,8 @@ context "Haml" do
haml 'foo' haml 'foo'
end end
Haml::Engine.expects(:new).with('foo', {:format => :html4, Haml::Engine.expects(:new).with('foo', {:filename => __FILE__,
:line => (__LINE__-4), :format => :html4,
:escape_html => true}).returns(stub(:render => 'foo')) :escape_html => true}).returns(stub(:render => 'foo'))
get_it '/' get_it '/'

View file

@ -253,30 +253,35 @@ module Sinatra
locals = options.delete(:locals) || locals || {} locals = options.delete(:locals) || locals || {}
# render template # render template
data = lookup_template(engine, template, views) data, options[:filename], options[:line] = lookup_template(engine, template, views)
output = __send__("render_#{engine}", template, data, options, locals) output = __send__("render_#{engine}", template, data, options, locals)
# render layout # render layout
if layout && data = lookup_layout(engine, layout, views) if layout
__send__("render_#{engine}", layout, data, options, {}) { output } data, options[:filename], options[:line] = lookup_layout(engine, layout, views)
else if data
output output = __send__("render_#{engine}", layout, data, options, {}) { output }
end
end end
output
end end
def lookup_template(engine, template, views_dir) def lookup_template(engine, template, views_dir, filename = nil, line = nil)
case template case template
when Symbol when Symbol
if cached = self.class.templates[template] if cached = self.class.templates[template]
lookup_template(engine, cached, views_dir) lookup_template(engine, cached[:template], views_dir, cached[:filename], cached[:line])
else else
path = ::File.join(views_dir, "#{template}.#{engine}") path = ::File.join(views_dir, "#{template}.#{engine}")
::File.read(path) [ ::File.read(path), path, 1 ]
end end
when Proc when Proc
template.call filename, line = self.class.caller_locations.first if filename.nil?
[ template.call, filename, line.to_i ]
when String when String
template filename, line = self.class.caller_locations.first if filename.nil?
[ template, filename, line.to_i ]
else else
raise ArgumentError raise ArgumentError
end end
@ -295,8 +300,13 @@ module Sinatra
instance = ::ERB.new(data, nil, nil, '@_out_buf') instance = ::ERB.new(data, nil, nil, '@_out_buf')
locals_assigns = locals.to_a.collect { |k,v| "#{k} = locals[:#{k}]" } locals_assigns = locals.to_a.collect { |k,v| "#{k} = locals[:#{k}]" }
src = "#{locals_assigns.join("\n")}\n#{instance.src}" filename = options.delete(:filename) || '(__ERB__)'
eval src, binding, '(__ERB__)', locals_assigns.length + 1 line = options.delete(:line) || 1
line -= 1 if instance.src =~ /^#coding:/
render_binding = binding
eval locals_assigns.join("\n"), render_binding
eval instance.src, render_binding, filename, line
@_out_buf, result = original_out_buf, @_out_buf @_out_buf, result = original_out_buf, @_out_buf
result result
end end
@ -311,9 +321,11 @@ module Sinatra
def render_builder(template, data, options, locals, &block) def render_builder(template, data, options, locals, &block)
options = { :indent => 2 }.merge(options) options = { :indent => 2 }.merge(options)
filename = options.delete(:filename) || '<BUILDER>'
line = options.delete(:line) || 1
xml = ::Builder::XmlMarkup.new(options) xml = ::Builder::XmlMarkup.new(options)
if data.respond_to?(:to_str) if data.respond_to?(:to_str)
eval data.to_str, binding, '<BUILDER>', 1 eval data.to_str, binding, filename, line
elsif data.kind_of?(Proc) elsif data.kind_of?(Proc)
data.call(xml) data.call(xml)
end end
@ -619,7 +631,8 @@ module Sinatra
# Define a named template. The block must return the template source. # Define a named template. The block must return the template source.
def template(name, &block) def template(name, &block)
templates[name] = block filename, line = caller_locations.first
templates[name] = { :filename => filename, :line => line, :template => block }
end end
# Define the layout template. The block must return the template source. # Define the layout template. The block must return the template source.
@ -631,19 +644,18 @@ module Sinatra
# when no file is specified. # when no file is specified.
def use_in_file_templates!(file=nil) def use_in_file_templates!(file=nil)
file ||= caller_files.first file ||= caller_files.first
app, data =
begin ::IO.read(file).split(/^__END__$/, 2) rescue nil
data = ::IO.read(file).split(/^__END__$/)[1]
rescue
data = nil
end
if data if data
data.gsub!(/\r\n/, "\n") data.gsub!(/\r\n/, "\n")
lines = app.count("\n") + 1
template = nil template = nil
data.each_line do |line| data.each_line do |line|
lines += 1
if line =~ /^@@\s*(.*)/ if line =~ /^@@\s*(.*)/
template = templates[$1.to_sym] = '' template = ''
templates[$1.to_sym] = { :filename => file, :line => lines, :template => template }
elsif template elsif template
template << line template << line
end end
@ -902,6 +914,7 @@ module Sinatra
send :define_method, message, &block send :define_method, message, &block
end end
public
CALLERS_TO_IGNORE = [ CALLERS_TO_IGNORE = [
/lib\/sinatra.*\.rb$/, # all sinatra code /lib\/sinatra.*\.rb$/, # all sinatra code
/\(.*\)/, # generated code /\(.*\)/, # generated code

View file

@ -0,0 +1,145 @@
require File.dirname(__FILE__) + '/helper'
require 'sass/error'
class RenderBacktraceTest < Test::Unit::TestCase
VIEWS = File.dirname(__FILE__) + '/views'
def assert_raise_at(filename, line, exception = RuntimeError)
f, l = nil
assert_raise(exception) do
begin
get('/')
rescue => e
f, l = e.backtrace.first.split(':')
raise
end
end
assert_equal(filename, f, "expected #{exception.name} in #{filename}, was #{f}")
assert_equal(line, l.to_i, "expected #{exception.name} in #{filename} at line #{line}, was at line #{l}")
end
def backtrace_app(&block)
mock_app {
use_in_file_templates!
set :views, RenderBacktraceTest::VIEWS
template :builder_template do
'raise "error"'
end
template :erb_template do
'<% raise "error" %>'
end
template :haml_template do
'%h1= raise "error"'
end
template :sass_template do
'+syntax-error'
end
get '/', &block
}
end
it "provides backtrace for Builder template" do
backtrace_app { builder :error }
assert_raise_at(File.join(VIEWS,'error.builder'), 2)
end
it "provides backtrace for ERB template" do
backtrace_app { erb :error }
assert_raise_at(File.join(VIEWS,'error.erb'), 2)
end
it "provides backtrace for HAML template" do
backtrace_app { haml :error }
assert_raise_at(File.join(VIEWS,'error.haml'), 2)
end
it "provides backtrace for Sass template" do
backtrace_app { sass :error }
assert_raise_at(File.join(VIEWS,'error.sass'), 2, Sass::SyntaxError)
end
it "provides backtrace for ERB template with locals" do
backtrace_app { erb :error, {}, :french => true }
assert_raise_at(File.join(VIEWS,'error.erb'), 3)
end
it "provides backtrace for HAML template with locals" do
backtrace_app { haml :error, {}, :french => true }
assert_raise_at(File.join(VIEWS,'error.haml'), 3)
end
it "provides backtrace for inline Builder string" do
backtrace_app { builder "raise 'Ack! Thbbbt!'"}
assert_raise_at(__FILE__, (__LINE__-1))
end
it "provides backtrace for inline ERB string" do
backtrace_app { erb "<% raise 'bidi-bidi-bidi' %>" }
assert_raise_at(__FILE__, (__LINE__-1))
end
it "provides backtrace for inline HAML string" do
backtrace_app { haml "%h1= raise 'Lions and tigers and bears! Oh, my!'" }
assert_raise_at(__FILE__, (__LINE__-1))
end
# it "provides backtrace for inline Sass string" do
# backtrace_app { sass '+buh-bye' }
# assert_raise_at(__FILE__, (__LINE__-1), Sass::SyntaxError)
# end
it "provides backtrace for named Builder template" do
backtrace_app { builder :builder_template }
assert_raise_at(__FILE__, (__LINE__-68))
end
it "provides backtrace for named ERB template" do
backtrace_app { erb :erb_template }
assert_raise_at(__FILE__, (__LINE__-70))
end
it "provides backtrace for named HAML template" do
backtrace_app { haml :haml_template }
assert_raise_at(__FILE__, (__LINE__-72))
end
# it "provides backtrace for named Sass template" do
# backtrace_app { sass :sass_template }
# assert_raise_at(__FILE__, (__LINE__-74), Sass::SyntaxError)
# end
it "provides backtrace for in file Builder template" do
backtrace_app { builder :builder_in_file }
assert_raise_at(__FILE__, (__LINE__+22))
end
it "provides backtrace for in file ERB template" do
backtrace_app { erb :erb_in_file }
assert_raise_at(__FILE__, (__LINE__+20))
end
it "provides backtrace for in file HAML template" do
backtrace_app { haml :haml_in_file }
assert_raise_at(__FILE__, (__LINE__+18))
end
# it "provides backtrace for in file Sass template" do
# backtrace_app { sass :sass_in_file }
# assert_raise_at(__FILE__, (__LINE__+16), Sass::SyntaxError)
# end
end
__END__
@@ builder_in_file
raise "bif"
@@ erb_in_file
<% raise "bam" %>
@@ haml_in_file
%h1= raise "pow"
@@ sass_in_file
+blam

View file

@ -72,8 +72,8 @@ class TemplatesTest < Test::Unit::TestCase
mock_app { mock_app {
use_in_file_templates! use_in_file_templates!
} }
assert_equal "this is foo\n\n", @app.templates[:foo] assert_equal "this is foo\n\n", @app.templates[:foo][:template]
assert_equal "X\n= yield\nX\n", @app.templates[:layout] assert_equal "X\n= yield\nX\n", @app.templates[:layout][:template]
end end
test 'use_in_file_templates simply ignores IO errors' do test 'use_in_file_templates simply ignores IO errors' do

3
test/views/error.builder Normal file
View file

@ -0,0 +1,3 @@
xml.error do
raise "goodbye"
end

3
test/views/error.erb Normal file
View file

@ -0,0 +1,3 @@
Hello <%= 'World' %>
<% raise 'Goodbye' unless defined?(french) && french %>
<% raise 'Au revoir' if defined?(french) && french %>

3
test/views/error.haml Normal file
View file

@ -0,0 +1,3 @@
%h1 Hello From Haml
= raise 'goodbye' unless defined?(french) && french
= raise 'au revoir' if defined?(french) && french

2
test/views/error.sass Normal file
View file

@ -0,0 +1,2 @@
#sass
+argle-bargle