Skip to content

Instantly share code, notes, and snippets.

@fbrubacher
Created March 13, 2010 22:31
Show Gist options
  • Save fbrubacher/331603 to your computer and use it in GitHub Desktop.
Save fbrubacher/331603 to your computer and use it in GitHub Desktop.
require 'thread'
require 'time'
require 'uri'
require 'rack'
require 'rack/builder'
require 'sinatra/showexceptions'
module Sinatra
VERSION = '0.9.4'
# The request object. See Rack::Request for more info:
# http://rack.rubyforge.org/doc/classes/Rack/Request.html
class Request < Rack::Request
def user_agent
@env['HTTP_USER_AGENT']
end
# Returns an array of acceptable media types for the response
def accept
@env['HTTP_ACCEPT'].to_s.split(',').map { |a| a.strip }
end
# Override Rack 0.9.x's #params implementation (see #72 in lighthouse)
def params
self.GET.update(self.POST)
rescue EOFError, Errno::ESPIPE
self.GET
end
end
# The response object. See Rack::Response and Rack::ResponseHelpers for
# more info:
# http://rack.rubyforge.org/doc/classes/Rack/Response.html
# http://rack.rubyforge.org/doc/classes/Rack/Response/Helpers.html
class Response < Rack::Response
def finish
@body = block if block_given?
if [204, 304].include?(status.to_i)
header.delete "Content-Type"
[status.to_i, header.to_hash, []]
else
body = @body || []
body = [body] if body.respond_to? :to_str
if body.respond_to?(:to_ary)
header["Content-Length"] = body.to_ary.
inject(0) { |len, part| len + part.bytesize }.to_s
end
[status.to_i, header.to_hash, body]
end
end
end
class NotFound < NameError #:nodoc:
def code ; 404 ; end
end
# Methods available to routes, before filters, and views.
module Helpers
# Set or retrieve the response status code.
def status(value=nil)
response.status = value if value
response.status
end
# Set or retrieve the response body. When a block is given,
# evaluation is deferred until the body is read with #each.
def body(value=nil, &block)
if block_given?
def block.each ; yield call ; end
response.body = block
else
response.body = value
end
end
# Halt processing and redirect to the URI provided.
def redirect(uri, *args)
status 302
response['Location'] = uri
halt(*args)
end
# Halt processing and return the error status provided.
def error(code, body=nil)
code, body = 500, code.to_str if code.respond_to? :to_str
response.body = body unless body.nil?
halt code
end
# Halt processing and return a 404 Not Found.
def not_found(body=nil)
error 404, body
end
# Set multiple response headers with Hash.
def headers(hash=nil)
response.headers.merge! hash if hash
response.headers
end
# Access the underlying Rack session.
def session
env['rack.session'] ||= {}
end
# Look up a media type by file extension in Rack's mime registry.
def media_type(type)
Base.media_type(type)
end
# Set the Content-Type of the response body given a media type or file
# extension.
def content_type(type, params={})
media_type = self.media_type(type)
fail "Unknown media type: %p" % type if media_type.nil?
if params.any?
params = params.collect { |kv| "%s=%s" % kv }.join(', ')
response['Content-Type'] = [media_type, params].join(";")
else
response['Content-Type'] = media_type
end
end
# Set the Content-Disposition to "attachment" with the specified filename,
# instructing the user agents to prompt to save.
def attachment(filename=nil)
response['Content-Disposition'] = 'attachment'
if filename
params = '; filename="%s"' % File.basename(filename)
response['Content-Disposition'] << params
end
end
# 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
# Rack response body used to deliver static files. The file contents are
# generated iteratively in 8K chunks.
class StaticFile < ::File #:nodoc:
alias_method :to_path, :path
def each
rewind
while buf = read(8192)
yield buf
end
end
end
# Set the last modified time of the resource (HTTP 'Last-Modified' header)
# and halt if conditional GET matches. The +time+ argument is a Time,
# DateTime, or other object that responds to +to_time+.
#
# When the current request includes an 'If-Modified-Since' header that
# matches the time specified, execution is immediately halted with a
# '304 Not Modified' response.
def last_modified(time)
time = time.to_time if time.respond_to?(:to_time)
time = time.httpdate if time.respond_to?(:httpdate)
response['Last-Modified'] = time
halt 304 if time == request.env['HTTP_IF_MODIFIED_SINCE']
time
end
# Set the response entity tag (HTTP 'ETag' header) and halt if conditional
# GET matches. The +value+ argument is an identifier that uniquely
# identifies the current version of the resource. The +strength+ argument
# indicates whether the etag should be used as a :strong (default) or :weak
# cache validator.
#
# When the current request includes an 'If-None-Match' header with a
# matching etag, execution is immediately halted. If the request method is
# GET or HEAD, a '304 Not Modified' response is sent.
def etag(value, kind=:strong)
raise TypeError, ":strong or :weak expected" if ![:strong,:weak].include?(kind)
value = '"%s"' % value
value = 'W/' + value if kind == :weak
response['ETag'] = value
# Conditional GET check
if etags = env['HTTP_IF_NONE_MATCH']
etags = etags.split(/\s*,\s*/)
halt 304 if etags.include?(value) || etags.include?('*')
end
end
## Sugar for redirect (example: redirect back)
def back ; request.referer ; end
end
# Template rendering methods. Each method takes a the name of a template
# to render as a Symbol and returns a String with the rendered output,
# as well as an optional hash with additional options.
#
# `template` is either the name or path of the template as symbol
# (Use `:'subdir/myview'` for views in subdirectories), or a string
# that will be rendered.
#
# Possible options are:
# :layout If set to false, no layout is rendered, otherwise
# the specified layout is used (Ignored for `sass`)
# :locals A hash with local variables that should be available
# in the template
module Templates
def erb(template, options={}, locals={})
require_warn('ERB') unless defined?(::ERB)
render :erb, template, options, locals
end
def haml(template, options={}, locals={})
require_warn('Haml') unless defined?(::Haml::Engine)
render :haml, template, options, locals
end
def sass(template, options={}, locals={})
require_warn('Sass') unless defined?(::Sass::Engine)
options[:layout] = false
render :sass, template, options, locals
end
def builder(template=nil, options={}, locals={}, &block)
require_warn('Builder') unless defined?(::Builder)
options, template = template, nil if template.is_a?(Hash)
template = lambda { block } if template.nil?
render :builder, template, options, locals
end
private
def render(engine, template, options={}, locals={})
# merge app-level options
options = self.class.send(engine).merge(options) if self.class.respond_to?(engine)
# extract generic options
layout = options.delete(:layout)
layout = :layout if layout.nil? || layout == true
views = options.delete(:views) || self.class.views || "./views"
locals = options.delete(:locals) || locals || {}
# render template
data, options[:filename], options[:line] = lookup_template(engine, template, views)
output = __send__("render_#{engine}", template, data, options, locals)
# render layout
if layout
data, options[:filename], options[:line] = lookup_layout(engine, layout, views)
if data
output = __send__("render_#{engine}", layout, data, options, locals) { output }
end
end
output
end
def lookup_template(engine, template, views_dir, filename = nil, line = nil)
case template
when Symbol
if cached = self.class.templates[template]
lookup_template(engine, cached[:template], views_dir, cached[:filename], cached[:line])
else
path = ::File.join(views_dir, "#{template}.#{engine}")
[ ::File.read(path), path, 1 ]
end
when Proc
filename, line = self.class.caller_locations.first if filename.nil?
[ template.call, filename, line.to_i ]
when String
filename, line = self.class.caller_locations.first if filename.nil?
[ template, filename, line.to_i ]
else
raise ArgumentError
end
end
def lookup_layout(engine, template, views_dir)
lookup_template(engine, template, views_dir)
rescue Errno::ENOENT
nil
end
def render_erb(template, data, options, locals, &block)
original_out_buf = defined?(@_out_buf) && @_out_buf
data = data.call if data.kind_of? Proc
instance = ::ERB.new(data, nil, nil, '@_out_buf')
locals_assigns = locals.to_a.collect { |k,v| "#{k} = locals[:#{k}]" }
filename = options.delete(:filename) || '(__ERB__)'
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
result
end
def render_haml(template, data, options, locals, &block)
::Haml::Engine.new(data, options).render(self, locals, &block)
end
def render_sass(template, data, options, locals, &block)
::Sass::Engine.new(data, options).render
end
def render_builder(template, data, options, locals, &block)
options = { :indent => 2 }.merge(options)
filename = options.delete(:filename) || '<BUILDER>'
line = options.delete(:line) || 1
xml = ::Builder::XmlMarkup.new(options)
if data.respond_to?(:to_str)
eval data.to_str, binding, filename, line
elsif data.kind_of?(Proc)
data.call(xml)
end
xml.target!
end
def require_warn(engine)
warn "Auto-require of #{engine} is deprecated; add require '#{engine}' to your app."
require engine.downcase
end
end
# Base class for all Sinatra applications and middleware.
class Base
include Rack::Utils
include Helpers
include Templates
attr_accessor :app
def initialize(app=nil)
@app = app
yield self if block_given?
end
# Rack call interface.
def call(env)
dup.call!(env)
end
attr_accessor :env, :request, :response, :params
def call!(env)
@env = env
@request = Request.new(env)
@response = Response.new
@params = nil
invoke { dispatch! }
invoke { error_block!(response.status) }
status, header, body = @response.finish
# Never produce a body on HEAD requests. Do retain the Content-Length
# unless it's "0", in which case we assume it was calculated erroneously
# for a manual HEAD response and remove it entirely.
if @env['REQUEST_METHOD'] == 'HEAD'
body = []
header.delete('Content-Length') if header['Content-Length'] == '0'
end
[status, header, body]
end
# Access options defined with Base.set.
def options
self.class
end
# Exit the current block, halts any further processing
# of the request, and returns the specified response.
def halt(*response)
response = response.first if response.length == 1
throw :halt, response
end
# Pass control to the next matching route.
# If there are no more matching routes, Sinatra will
# return a 404 response.
def pass
throw :pass
end
# Forward the request to the downstream app -- middleware only.
def forward
fail "downstream app not set" unless @app.respond_to? :call
status, headers, body = @app.call(@request.env)
@response.status = status
@response.body = body
@response.headers.merge! headers
nil
end
private
# Run before filters and then locate and run a matching route.
def route!
# enable nested params in Rack < 1.0; allow indifferent access
@params =
if Rack::Utils.respond_to?(:parse_nested_query)
indifferent_params(@request.params)
else
nested_params(@request.params)
end
# before filters
self.class.filters.each { |block| instance_eval(&block) }
# routes
if routes = self.class.routes[@request.request_method]
original_params = @params
path = unescape(@request.path_info)
routes.each do |pattern, keys, conditions, block|
if match = pattern.match(path)
values = match.captures.to_a
params =
if keys.any?
keys.zip(values).inject({}) do |hash,(k,v)|
if k == 'splat'
(hash[k] ||= []) << v
else
hash[k] = v
end
hash
end
elsif values.any?
{'captures' => values}
else
{}
end
@params = original_params.merge(params)
@block_params = values
catch(:pass) do
conditions.each { |cond|
throw :pass if instance_eval(&cond) == false }
route_eval(&block)
end
end
end
end
route_missing
end
# Run a route block and throw :halt with the result.
def route_eval(&block)
throw :halt, instance_eval(&block)
end
# No matching route was found or all routes passed. The default
# implementation is to forward the request downstream when running
# as middleware (@app is non-nil); when no downstream app is set, raise
# a NotFound exception. Subclasses can override this method to perform
# custom route miss logic.
def route_missing
if @app
forward
else
raise NotFound
end
end
# Enable string or symbol key access to the nested params hash.
def indifferent_params(params)
params = indifferent_hash.merge(params)
params.each do |key, value|
next unless value.is_a?(Hash)
params[key] = indifferent_params(value)
end
end
# Recursively replace the params hash with a nested indifferent
# hash. Rack 1.0 has a built in implementation of this method - remove
# this once Rack 1.0 is required.
def nested_params(params)
return indifferent_hash.merge(params) if !params.keys.join.include?('[')
params.inject indifferent_hash do |res, (key,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
# 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)
@response.body = [res]
when res.respond_to?(:to_ary)
res = res.to_ary
if Fixnum === res.first
if res.length == 3
@response.status, headers, body = res
@response.body = body if body
headers.each { |k, v| @response.headers[k] = v } if headers
elsif res.length == 2
@response.status = res.first
@response.body = res.last
else
raise TypeError, "#{res.inspect} not supported"
end
else
@response.body = res
end
when res.respond_to?(:each)
@response.body = res
when (100...599) === res
@response.status = res
end
res
end
# Dispatch a request with error handling.
def dispatch!
route!
rescue NotFound => boom
handle_not_found!(boom)
rescue ::Exception => boom
handle_exception!(boom)
end
def handle_not_found!(boom)
@env['sinatra.error'] = boom
@response.status = 404
@response.body = ['<h1>Not Found</h1>']
error_block! boom.class, NotFound
end
def handle_exception!(boom)
@env['sinatra.error'] = boom
dump_errors!(boom) if options.dump_errors?
raise boom if options.raise_errors? || options.show_exceptions?
@response.status = 500
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 dump_errors!(boom)
backtrace = clean_backtrace(boom.backtrace)
msg = ["#{boom.class} - #{boom.message}:",
*backtrace].join("\n ")
@env['rack.errors'].write(msg)
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 = []
@errors = {}
@prototype = nil
@extensions = []
class << self
attr_accessor :routes, :filters, :conditions, :templates,
:middleware, :errors
# Sets an option to the given value. If the value is a proc,
# the proc will be called every time the option is accessed.
def set(option, value=self)
if value.kind_of?(Proc)
metadef(option, &value)
metadef("#{option}?") { !!__send__(option) }
metadef("#{option}=") { |val| set(option, Proc.new{val}) }
elsif value == self && option.respond_to?(:to_hash)
option.to_hash.each { |k,v| set(k, v) }
elsif respond_to?("#{option}=")
__send__ "#{option}=", value
else
set option, Proc.new{value}
end
self
end
# Same as calling `set :option, true` for each of the given options.
def enable(*opts)
opts.each { |key| set(key, true) }
end
# Same as calling `set :option, false` for each of the given options.
def disable(*opts)
opts.each { |key| set(key, false) }
end
# Define a custom error handler. Optionally takes either an Exception
# class, or an HTTP status code to specify which errors should be
# handled.
def error(codes=Exception, &block)
if codes.respond_to? :each
codes.each { |err| error(err, &block) }
else
@errors[codes] = block
end
end
# Sugar for `error(404) { ... }`
def not_found(&block)
error 404, &block
end
# Define a named template. The block must return the template source.
def template(name, &block)
filename, line = caller_locations.first
templates[name] = { :filename => filename, :line => line, :template => block }
end
# Define the layout template. The block must return the template source.
def layout(name=:layout, &block)
template name, &block
end
# Load embeded templates from the file; uses the caller's __FILE__
# when no file is specified.
def use_in_file_templates!(file=nil)
file ||= caller_files.first
app, data =
::IO.read(file).split(/^__END__$/, 2) rescue nil
if data
data.gsub!(/\r\n/, "\n")
lines = app.count("\n") + 1
template = nil
data.each_line do |line|
lines += 1
if line =~ /^@@\s*(.*)/
template = ''
templates[$1.to_sym] = { :filename => file, :line => lines, :template => template }
elsif template
template << line
end
end
end
end
# Look up a media type by file extension in Rack's mime registry.
def media_type(type)
return type if type.nil? || type.to_s.include?('/')
type = ".#{type}" unless type.to_s[0] == ?.
Rack::Mime.mime_type(type, nil)
end
# Define a before filter. Filters are run before all requests
# within the same context as route handlers and may access/modify the
# request and response.
def before(&block)
@filters << block
end
# Add a route condition. The route is considered non-matching when the
# block returns false.
def condition(&block)
@conditions << block
end
private
def host_name(pattern)
condition { pattern === request.host }
end
def user_agent(pattern)
condition {
if request.user_agent =~ pattern
@params[:agent] = $~[1..-1]
true
else
false
end
}
end
alias_method :agent, :user_agent
def provides(*types)
types = [types] unless types.kind_of? Array
types.map!{|t| media_type(t)}
condition {
matching_types = (request.accept & types)
unless matching_types.empty?
response.headers['Content-Type'] = matching_types.first
true
else
false
end
}
end
public
# Defining a `GET` handler also automatically defines
# a `HEAD` handler.
def get(path, opts={}, &block)
conditions = @conditions.dup
route('GET', path, opts, &block)
@conditions = conditions
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
def head(path, opts={}, &bk); route 'HEAD', path, opts, &bk end
private
def route(verb, path, options={}, &block)
# Because of self.options.host
host_name(options.delete(:host)) if options.key?(:host)
options.each {|option, args| send(option, *args)}
pattern, keys = compile(path)
conditions, @conditions = @conditions, []
define_method "#{verb} #{path}", &block
unbound_method = instance_method("#{verb} #{path}")
block =
if block.arity != 0
lambda { unbound_method.bind(self).call(*@block_params) }
else
lambda { unbound_method.bind(self).call }
end
invoke_hook(:route_added, verb, path, block)
(routes[verb] ||= []).
push([pattern, keys, conditions, block]).last
end
def invoke_hook(name, *args)
extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
end
def compile(path)
keys = []
if path.respond_to? :to_str
special_chars = %w{. + ( )}
pattern =
path.to_str.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?(:keys) && path.respond_to?(:match)
[path, path.keys]
elsif path.respond_to? :match
[path, keys]
else
raise TypeError, path
end
end
public
# Makes the methods defined in the block and in the Modules given
# in `extensions` available to the handlers and templates
def helpers(*extensions, &block)
class_eval(&block) if block_given?
include(*extensions) if extensions.any?
end
def extensions
(@extensions + (superclass.extensions rescue [])).uniq
end
def register(*extensions, &block)
extensions << Module.new(&block) if block_given?
@extensions += extensions
extensions.each do |extension|
extend extension
extension.registered(self) if extension.respond_to?(:registered)
end
end
def development?; environment == :development end
def production?; environment == :production end
def test?; environment == :test end
# Set configuration options for Sinatra and/or the app.
# Allows scoping of settings for certain environments.
def configure(*envs, &block)
yield self if envs.empty? || envs.include?(environment.to_sym)
end
# Use the specified Rack middleware
def use(middleware, *args, &block)
@prototype = nil
@middleware << [middleware, args, block]
end
# Run the Sinatra app as a self-hosted server using
# Thin, Mongrel or WEBrick (in that order)
def run!(options={})
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}" unless handler_name =~/cgi/i
handler.run self, :Host => host, :Port => port do |server|
trap(:INT) do
## Use thins' hard #stop! if available, otherwise just #stop
server.respond_to?(:stop!) ? server.stop! : server.stop
puts "\n== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i
end
end
rescue Errno::EADDRINUSE => e
puts "== Someone is already performing on port #{port}!"
end
# The prototype instance used to process requests.
def prototype
@prototype ||= new
end
# Create a new instance of the class fronted by its middleware
# pipeline. The object is guaranteed to respond to #call but may not be
# an instance of the class new was called on.
def new(*args, &bk)
builder = Rack::Builder.new
builder.use Rack::Session::Cookie if sessions? && !test?
builder.use Rack::CommonLogger if logging?
builder.use Rack::MethodOverride if methodoverride?
builder.use ShowExceptions if show_exceptions?
@middleware.each { |c,a,b| builder.use(c, *a, &b) }
builder.run super
builder.to_app
end
def call(env)
synchronize { prototype.call(env) }
end
def reset!(base=superclass)
@routes = base.dupe_routes
@templates = base.templates.dup
@conditions = []
@filters = base.filters.dup
@errors = base.errors.dup
@middleware = base.middleware.dup
@prototype = nil
@extensions = []
end
protected
def dupe_routes
routes.inject({}) do |hash,(request_method,routes)|
hash[request_method] = routes.dup
hash
end
end
private
def detect_rack_handler
servers = Array(self.server)
servers.each do |server_name|
begin
return Rack::Handler.get(server_name.downcase)
rescue LoadError
rescue NameError
end
end
fail "Server handler (#{servers.join(',')}) not found."
end
def inherited(subclass)
subclass.reset! self
super
end
@@mutex = Mutex.new
def synchronize(&block)
if lock?
@@mutex.synchronize(&block)
else
yield
end
end
def metadef(message, &block)
(class << self; self; end).
send :define_method, message, &block
end
public
CALLERS_TO_IGNORE = [
/\/sinatra(\/(base|main|showexceptions|compat))?\.rb$/, # all sinatra code
/\(.*\)/, # generated code
/custom_require\.rb$/, # rubygems require hacks
/active_support/, # active_support require hacks
] unless self.const_defined?('CALLERS_TO_IGNORE')
# add rubinius (and hopefully other VM impls) ignore patterns ...
CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS) if defined?(RUBY_IGNORE_CALLERS)
# Like Kernel#caller but excluding certain magic entries and without
# line / method information; the resulting array contains filenames only.
def caller_files
caller_locations.
map { |file,line| file }
end
def caller_locations
caller(1).
map { |line| line.split(/:(?=\d|in )/)[0,2] }.
reject { |file,line| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
end
end
set :raise_errors, true
set :dump_errors, false
set :clean_trace, true
set :show_exceptions, false
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, %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)) }
set :views, Proc.new { root && File.join(root, 'views') }
set :public, Proc.new { root && File.join(root, 'public') }
set :lock, false
# static files route
get(/.*[^\/]$/) do
pass unless options.static? && options.public?
public_dir = File.expand_path(options.public)
path = File.expand_path(public_dir + unescape(request.path_info))
pass if path[0, public_dir.length] != public_dir
pass unless File.file?(path)
send_file path, :disposition => nil
end
error ::Exception do
response.status = 500
content_type 'text/html'
'<h1>Internal Server Error</h1>'
end
configure :development do
get '/__sinatra__/:image.png' do
filename = File.dirname(__FILE__) + "/images/#{params[:image]}.png"
content_type :png
send_file filename
end
error NotFound do
content_type 'text/html'
(<<-HTML).gsub(/^ {8}/, '')
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
body { text-align:center;font-family:helvetica,arial;font-size:22px;
color:#888;margin:20px}
#c {margin:0 auto;width:500px;text-align:left}
</style>
</head>
<body>
<h2>Sinatra doesn't know this ditty.</h2>
<img src='/__sinatra__/404.png'>
<div id="c">
Try this:
<pre>#{request.request_method.downcase} '#{request.path_info}' do\n "Hello World"\nend</pre>
</div>
</body>
</html>
HTML
end
end
end
# Base class for classic style (top-level) applications.
class Default < Base
set :raise_errors, Proc.new { test? }
set :show_exceptions, Proc.new { development? }
set :dump_errors, true
set :sessions, false
set :logging, Proc.new { ! test? }
set :methodoverride, true
set :static, true
set :run, Proc.new { ! test? }
def self.register(*extensions, &block) #:nodoc:
added_methods = extensions.map {|m| m.public_instance_methods }.flatten
Delegator.delegate(*added_methods)
super(*extensions, &block)
end
end
# The top-level Application. All DSL methods executed on main are delegated
# to this class.
class Application < Default
end
# Sinatra delegation mixin. Mixing this module into an object causes all
# methods to be delegated to the Sinatra::Application class. Used primarily
# at the top-level.
module Delegator #:nodoc:
def self.delegate(*methods)
methods.each do |method_name|
eval <<-RUBY, binding, '(__DELEGATE__)', 1
def #{method_name}(*args, &b)
::Sinatra::Application.send(#{method_name.inspect}, *args, &b)
end
private #{method_name.inspect}
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
# Create a new Sinatra application. The block is evaluated in the new app's
# class scope.
def self.new(base=Base, options={}, &block)
base = Class.new(base)
base.send :class_eval, &block if block_given?
base
end
# Extend the top-level DSL with the modules provided.
def self.register(*extensions, &block)
Default.register(*extensions, &block)
end
# Include the helper modules provided in Sinatra's request context.
def self.helpers(*extensions, &block)
Default.helpers(*extensions, &block)
end
end
class String #:nodoc:
# Define String#each under 1.9 for Rack compatibility. This should be
# removed once Rack is fully 1.9 compatible.
alias_method :each, :each_line unless ''.respond_to? :each
# Define String#bytesize as an alias to String#length for Ruby 1.8.6 and
# earlier.
alias_method :bytesize, :length unless ''.respond_to? :bytesize
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment