lib/sinatra/base.rb in Syd-sinatra-0.9.0.2 vs lib/sinatra/base.rb in Syd-sinatra-0.9.0.4
- old
+ new
@@ -123,27 +123,37 @@
params = '; filename="%s"' % File.basename(filename)
response['Content-Disposition'] << params
end
end
- # Use the contents of the file as the response body and attempt to
+ # Use the contents of the file at +path+ as the response body.
def send_file(path, opts={})
stat = File.stat(path)
last_modified stat.mtime
+
content_type media_type(opts[:type]) ||
media_type(File.extname(path)) ||
response['Content-Type'] ||
'application/octet-stream'
+
response['Content-Length'] ||= (opts[:length] || stat.size).to_s
+
+ if opts[:disposition] == 'attachment' || opts[:filename]
+ attachment opts[:filename] || path
+ elsif opts[:disposition] == 'inline'
+ response['Content-Disposition'] = 'inline'
+ end
+
halt StaticFile.open(path, 'rb')
rescue Errno::ENOENT
not_found
end
class StaticFile < ::File #:nodoc:
alias_method :to_path, :path
def each
+ rewind
while buf = read(8192)
yield buf
end
end
end
@@ -241,11 +251,10 @@
instance = ::ERB.new(data)
locals = options[:locals] || {}
locals_assigns = locals.to_a.collect { |k,v| "#{k} = locals[:#{k}]" }
src = "#{locals_assigns.join("\n")}\n#{instance.src}"
eval src, binding, '(__ERB__)', locals_assigns.length + 1
- instance.result(binding)
end
def haml(template, options={})
require 'haml' unless defined? ::Haml
options[:options] ||= self.class.haml if self.class.respond_to? :haml
@@ -308,40 +317,47 @@
def call!(env)
@env = env
@request = Request.new(env)
@response = Response.new
@params = nil
- error_detection { dispatch! }
+
+ invoke { dispatch! }
+ invoke { error_block!(response.status) }
+
+ @response.body = [] if @env['REQUEST_METHOD'] == 'HEAD'
@response.finish
end
def options
self.class
end
def halt(*response)
- throw :halt, *response
+ response = response.first if response.length == 1
+ throw :halt, response
end
def pass
throw :pass
end
private
- def dispatch!
- self.class.filters.each do |block|
- res = catch(:halt) { instance_eval(&block) ; :continue }
- return unless res == :continue
- end
+ # Run before filters and then locate and run a matching route.
+ def route!
+ @params = nested_params(@request.params)
+ # before filters
+ self.class.filters.each { |block| instance_eval(&block) }
+
+ # routes
if routes = self.class.routes[@request.request_method]
+ original_params = @params
path = @request.path_info
- original_params = nested_params(@request.params)
- routes.each do |pattern, keys, conditions, method_name|
- if pattern =~ path
- values = $~.captures.map{|val| val && unescape(val) }
+ routes.each do |pattern, keys, conditions, block|
+ if match = pattern.match(path)
+ values = match.captures.map{|val| val && unescape(val) }
params =
if keys.any?
keys.zip(values).inject({}) do |hash,(k,v)|
if k == 'splat'
(hash[k] ||= []) << v
@@ -355,38 +371,42 @@
else
{}
end
@params = original_params.merge(params)
- catch(:pass) {
+ catch(:pass) do
conditions.each { |cond|
throw :pass if instance_eval(&cond) == false }
- return invoke(method_name)
- }
+ throw :halt, instance_eval(&block)
+ end
end
end
end
+
raise NotFound
end
def nested_params(params)
return indifferent_hash.merge(params) if !params.keys.join.include?('[')
params.inject indifferent_hash do |res, (key,val)|
- if key =~ /\[.*\]/
- splat = key.scan(/(^[^\[]+)|\[([^\]]+)\]/).flatten.compact
- head, last = splat[0..-2], splat[-1]
- head.inject(res){ |s,v| s[v] ||= indifferent_hash }[last] = val
+ if key.include?('[')
+ head = key.split(/[\]\[]+/)
+ last = head.pop
+ head.inject(res){ |hash,k| hash[k] ||= indifferent_hash }[last] = val
+ else
+ res[key] = val
end
res
end
end
def indifferent_hash
Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
end
- def invoke(block)
+ # Run the block with 'throw :halt' support and apply result to the response.
+ def invoke(&block)
res = catch(:halt) { instance_eval(&block) }
return if res.nil?
case
when res.respond_to?(:to_str)
@@ -414,36 +434,54 @@
end
res
end
- def error_detection
- errmap = self.class.errors
- yield
+ # Dispatch a request with error handling.
+ def dispatch!
+ route!
rescue NotFound => boom
@env['sinatra.error'] = boom
@response.status = 404
@response.body = ['<h1>Not Found</h1>']
- handler = errmap[boom.class] || errmap[NotFound]
- invoke handler unless handler.nil?
+ error_block! boom.class, NotFound
+
rescue ::Exception => boom
@env['sinatra.error'] = boom
if options.dump_errors?
- msg = ["#{boom.class} - #{boom.message}:", *boom.backtrace].join("\n ")
- @env['rack.errors'] << msg
+ backtrace = clean_backtrace(boom.backtrace)
+ msg = ["#{boom.class} - #{boom.message}:", *backtrace].join("\n ")
+ @env['rack.errors'].write(msg)
end
raise boom if options.raise_errors?
@response.status = 500
- invoke errmap[boom.class] || errmap[Exception]
- ensure
- if @response.status >= 400 && errmap.key?(response.status)
- invoke errmap[response.status]
+ error_block! boom.class, Exception
+ end
+
+ # Find an custom error block for the key(s) specified.
+ def error_block!(*keys)
+ errmap = self.class.errors
+ keys.each do |key|
+ if block = errmap[key]
+ res = instance_eval(&block)
+ return res
+ end
end
+ nil
end
+ def clean_backtrace(trace)
+ return trace unless options.clean_trace?
+
+ trace.reject { |line|
+ line =~ /lib\/sinatra.*\.rb/ ||
+ (defined?(Gem) && line.include?(Gem.dir))
+ }.map! { |line| line.gsub(/^\.\//, '') }
+ end
+
@routes = {}
@filters = []
@conditions = []
@templates = {}
@middleware = []
@@ -567,11 +605,11 @@
def get(path, opts={}, &block)
conditions = @conditions.dup
route('GET', path, opts, &block)
@conditions = conditions
- head(path, opts) { invoke(block) ; [] }
+ route('HEAD', path, opts, &block)
end
def put(path, opts={}, &bk); route 'PUT', path, opts, &bk; end
def post(path, opts={}, &bk); route 'POST', path, opts, &bk; end
def delete(path, opts={}, &bk); route 'DELETE', path, opts, &bk; end
@@ -595,29 +633,38 @@
end
def compile(path)
keys = []
if path.respond_to? :to_str
+ special_chars = %w{. + ( )}
pattern =
- URI.encode(path).gsub(/((:\w+)|\*)/) do |match|
- if match == "*"
+ URI.encode(path).gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
+ case match
+ when "*"
keys << 'splat'
"(.*?)"
+ when *special_chars
+ Regexp.escape(match)
else
keys << $2[1..-1]
"([^/?&#\.]+)"
end
end
[/^#{pattern}$/, keys]
- elsif path.respond_to? :=~
+ elsif path.respond_to? :match
[path, keys]
else
raise TypeError, path
end
end
public
+ def helpers(*modules, &block)
+ include *modules unless modules.empty?
+ class_eval(&block) if block
+ end
+
def development? ; environment == :development ; end
def test? ; environment == :test ; end
def production? ; environment == :production ; end
def configure(*envs, &block)
@@ -628,12 +675,12 @@
reset_middleware
@middleware << [middleware, args, block]
end
def run!(options={})
- set(options)
- handler = Rack::Handler.get(server)
+ set options
+ handler = detect_rack_handler
handler_name = handler.name.gsub(/.*::/, '')
puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
"on #{port} for #{environment} with backup from #{handler_name}"
handler.run self, :Host => host, :Port => port do |server|
trap(:INT) do
@@ -650,10 +697,22 @@
construct_middleware if @callsite.nil?
@callsite.call(env)
end
private
+ def detect_rack_handler
+ servers = Array(self.server)
+ servers.each do |server_name|
+ begin
+ return Rack::Handler.get(server_name)
+ rescue LoadError
+ rescue NameError
+ end
+ end
+ fail "Server handler (#{servers.join(',')}) not found."
+ end
+
def construct_middleware(builder=Rack::Builder.new)
builder.use Rack::Session::Cookie if sessions?
builder.use Rack::CommonLogger if logging?
builder.use Rack::MethodOverride if methodoverride?
@middleware.each { |c, args, bk| builder.use(c, *args, &bk) }
@@ -689,18 +748,19 @@
end
end
set :raise_errors, true
set :dump_errors, false
+ set :clean_trace, true
set :sessions, false
set :logging, false
set :methodoverride, false
set :static, false
set :environment, (ENV['RACK_ENV'] || :development).to_sym
set :run, false
- set :server, (defined?(Rack::Handler::Thin) ? "thin" : "mongrel")
+ set :server, %w[thin mongrel webrick]
set :host, '0.0.0.0'
set :port, 4567
set :app_file, nil
set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
@@ -769,11 +829,11 @@
</head>
<body>
<div id="c">
<img src="/__sinatra__/500.png">
<h1>#{escape_html(heading)}</h1>
- <pre class='trace'>#{escape_html(err.backtrace.join("\n"))}</pre>
+ <pre>#{escape_html(clean_backtrace(err.backtrace) * "\n")}</pre>
<h2>Params</h2>
<pre>#{escape_html(params.inspect)}</pre>
</div>
</body>
</html>
@@ -788,52 +848,65 @@
set :sessions, false
set :logging, true
set :methodoverride, true
set :static, true
set :run, false
- set :reload, Proc.new { app_file? && development? }
+ set :reload, Proc.new { app_file? && app_file !~ /\.ru$/i && development? }
+ set :lock, Proc.new { reload? }
def self.reloading?
@reloading ||= false
end
def self.configure(*envs)
super unless reloading?
end
def self.call(env)
- reload! if reload?
- super
+ synchronize do
+ reload! if reload?
+ super
+ end
end
def self.reload!
@reloading = true
superclass.send :inherited, self
$LOADED_FEATURES.delete("sinatra.rb")
::Kernel.load app_file
@reloading = false
end
+ private
+ @@mutex = Mutex.new
+ def self.synchronize(&block)
+ if lock?
+ @@mutex.synchronize(&block)
+ else
+ yield
+ end
+ end
end
class Application < Default
end
module Delegator
- METHODS = %w[
- get put post delete head template layout before error not_found
- configures configure set set_option set_options enable disable use
- development? test? production? use_in_file_templates!
- ]
-
- METHODS.each do |method_name|
- eval <<-RUBY, binding, '(__DELEGATE__)', 1
- def #{method_name}(*args, &b)
- ::Sinatra::Application.#{method_name}(*args, &b)
- end
- private :#{method_name}
- RUBY
+ def self.delegate(*methods)
+ methods.each do |method_name|
+ eval <<-RUBY, binding, '(__DELEGATE__)', 1
+ def #{method_name}(*args, &b)
+ ::Sinatra::Application.#{method_name}(*args, &b)
+ end
+ private :#{method_name}
+ RUBY
+ end
end
+
+ delegate :get, :put, :post, :delete, :head, :template, :layout, :before,
+ :error, :not_found, :configures, :configure, :set, :set_option,
+ :set_options, :enable, :disable, :use, :development?, :test?,
+ :production?, :use_in_file_templates!, :helpers
end
def self.new(base=Base, options={}, &block)
base = Class.new(base)
base.send :class_eval, &block if block_given?