mirror of
https://github.com/sinatra/sinatra
synced 2023-03-27 23:18:01 -04:00
parent
801163e9f3
commit
cfdf97d495
8 changed files with 199 additions and 27 deletions
|
@ -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 '/'
|
||||||
|
|
|
@ -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
|
||||||
|
|
145
test/render_backtrace_test.rb
Normal file
145
test/render_backtrace_test.rb
Normal 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
|
|
@ -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
3
test/views/error.builder
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
xml.error do
|
||||||
|
raise "goodbye"
|
||||||
|
end
|
3
test/views/error.erb
Normal file
3
test/views/error.erb
Normal 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
3
test/views/error.haml
Normal 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
2
test/views/error.sass
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
#sass
|
||||||
|
+argle-bargle
|
Loading…
Reference in a new issue