mirror of
https://github.com/sinatra/sinatra
synced 2023-03-27 23:18:01 -04:00
1489 lines
44 KiB
Ruby
1489 lines
44 KiB
Ruby
Dir[File.dirname(__FILE__) + "/../vendor/*"].each do |l|
|
|
$:.unshift "#{File.expand_path(l)}/lib"
|
|
end
|
|
|
|
require 'rack'
|
|
|
|
require 'rubygems'
|
|
require 'time'
|
|
require 'ostruct'
|
|
require "uri"
|
|
|
|
if ENV['SWIFT']
|
|
require 'swiftcore/swiftiplied_mongrel'
|
|
puts "Using Swiftiplied Mongrel"
|
|
elsif ENV['EVENT']
|
|
require 'swiftcore/evented_mongrel'
|
|
puts "Using Evented Mongrel"
|
|
end
|
|
|
|
class Class
|
|
def dslify_writer(*syms)
|
|
syms.each do |sym|
|
|
class_eval <<-end_eval
|
|
def #{sym}(v=nil)
|
|
self.send "#{sym}=", v if v
|
|
v
|
|
end
|
|
end_eval
|
|
end
|
|
end
|
|
end
|
|
|
|
module Rack #:nodoc:
|
|
|
|
class Request #:nodoc:
|
|
|
|
# Set of request method names allowed via the _method parameter hack. By default,
|
|
# all request methods defined in RFC2616 are included, with the exception of
|
|
# TRACE and CONNECT.
|
|
POST_TUNNEL_METHODS_ALLOWED = %w( PUT DELETE OPTIONS HEAD )
|
|
|
|
# Return the HTTP request method with support for method tunneling using the POST
|
|
# _method parameter hack. If the real request method is POST and a _method param is
|
|
# given and the value is one defined in +POST_TUNNEL_METHODS_ALLOWED+, return the value
|
|
# of the _method param instead.
|
|
def request_method
|
|
if post_tunnel_method_hack?
|
|
params['_method'].upcase
|
|
else
|
|
@env['REQUEST_METHOD']
|
|
end
|
|
end
|
|
|
|
def user_agent
|
|
@env['HTTP_USER_AGENT']
|
|
end
|
|
|
|
private
|
|
|
|
# Return truthfully if and only if the following conditions are met: 1.) the
|
|
# *actual* request method is POST, 2.) the request content-type is one of
|
|
# 'application/x-www-form-urlencoded' or 'multipart/form-data', 3.) there is a
|
|
# "_method" parameter in the POST body (not in the query string), and 4.) the
|
|
# method parameter is one of the verbs listed in the POST_TUNNEL_METHODS_ALLOWED
|
|
# list.
|
|
def post_tunnel_method_hack?
|
|
@env['REQUEST_METHOD'] == 'POST' &&
|
|
POST_TUNNEL_METHODS_ALLOWED.include?(self.POST.fetch('_method', '').upcase)
|
|
end
|
|
|
|
end
|
|
|
|
module Utils
|
|
extend self
|
|
end
|
|
|
|
end
|
|
|
|
module Sinatra
|
|
extend self
|
|
|
|
module Version
|
|
MAJOR = '0'
|
|
MINOR = '2'
|
|
REVISION = '3'
|
|
def self.combined
|
|
[MAJOR, MINOR, REVISION].join('.')
|
|
end
|
|
end
|
|
|
|
class NotFound < RuntimeError; end
|
|
class ServerError < RuntimeError; end
|
|
|
|
Result = Struct.new(:block, :params, :status) unless defined?(Result)
|
|
|
|
def options
|
|
application.options
|
|
end
|
|
|
|
def application
|
|
@app ||= Application.new
|
|
end
|
|
|
|
def application=(app)
|
|
@app = app
|
|
end
|
|
|
|
def port
|
|
application.options.port
|
|
end
|
|
|
|
def env
|
|
application.options.env
|
|
end
|
|
|
|
# Deprecated: use application instead of build_application.
|
|
alias :build_application :application
|
|
|
|
def server
|
|
options.server ||= defined?(Rack::Handler::Thin) ? "thin" : "mongrel"
|
|
# Convert the server into the actual handler name
|
|
handler = options.server.capitalize.sub(/cgi$/, 'CGI')
|
|
@server ||= eval("Rack::Handler::#{handler}")
|
|
end
|
|
|
|
def run
|
|
begin
|
|
puts "== Sinatra has taken the stage on port #{port} for #{env} with backup by #{server.name}"
|
|
require 'pp'
|
|
server.run(application, :Port => port) do |server|
|
|
trap(:INT) do
|
|
server.stop
|
|
puts "\n== Sinatra has ended his set (crowd applauds)"
|
|
end
|
|
end
|
|
rescue Errno::EADDRINUSE => e
|
|
puts "== Someone is already performing on port #{port}!"
|
|
end
|
|
end
|
|
|
|
class Event
|
|
|
|
URI_CHAR = '[^/?:,&#\.]'.freeze unless defined?(URI_CHAR)
|
|
PARAM = /(:(#{URI_CHAR}+)|\*)/.freeze unless defined?(PARAM)
|
|
SPLAT = /(.*?)/
|
|
attr_reader :path, :block, :param_keys, :pattern, :options
|
|
|
|
def initialize(path, options = {}, &b)
|
|
@path = URI.encode(path)
|
|
@block = b
|
|
@param_keys = []
|
|
@options = options
|
|
splats = 0
|
|
regex = @path.to_s.gsub(PARAM) do |match|
|
|
if match == "*"
|
|
@param_keys << "_splat_#{splats}"
|
|
splats += 1
|
|
SPLAT.to_s
|
|
else
|
|
@param_keys << $2
|
|
"(#{URI_CHAR}+)"
|
|
end
|
|
end
|
|
|
|
@pattern = /^#{regex}$/
|
|
end
|
|
|
|
def invoke(request)
|
|
params = {}
|
|
if agent = options[:agent]
|
|
return unless request.user_agent =~ agent
|
|
params[:agent] = $~[1..-1]
|
|
end
|
|
if host = options[:host]
|
|
return unless host === request.host
|
|
end
|
|
return unless pattern =~ request.path_info.squeeze('/')
|
|
params.merge!(param_keys.zip($~.captures.map(&:from_param)).to_hash)
|
|
splats = params.select { |k, v| k =~ /^_splat_\d+$/ }.sort.map(&:last)
|
|
unless splats.empty?
|
|
params.delete_if { |k, v| k =~ /^_splat_\d+$/ }
|
|
params["splat"] = splats
|
|
end
|
|
Result.new(block, params, 200)
|
|
end
|
|
|
|
end
|
|
|
|
class Error
|
|
|
|
attr_reader :code, :block
|
|
|
|
def initialize(code, &b)
|
|
@code, @block = code, b
|
|
end
|
|
|
|
def invoke(request)
|
|
Result.new(block, {}, 404)
|
|
end
|
|
|
|
end
|
|
|
|
class Static
|
|
|
|
def invoke(request)
|
|
return unless File.file?(
|
|
Sinatra.application.options.public + request.path_info.http_unescape
|
|
)
|
|
Result.new(block, {}, 200)
|
|
end
|
|
|
|
def block
|
|
Proc.new do
|
|
send_file Sinatra.application.options.public + request.path_info.http_unescape,
|
|
:disposition => nil
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
# Adapted from actionpack
|
|
# Methods for sending files and streams to the browser instead of rendering.
|
|
module Streaming
|
|
DEFAULT_SEND_FILE_OPTIONS = {
|
|
:type => 'application/octet-stream'.freeze,
|
|
:disposition => 'attachment'.freeze,
|
|
:stream => true,
|
|
:buffer_size => 4096
|
|
}.freeze
|
|
|
|
class MissingFile < RuntimeError; end
|
|
|
|
class FileStreamer
|
|
|
|
attr_reader :path, :options
|
|
|
|
def initialize(path, options)
|
|
@path, @options = path, options
|
|
end
|
|
|
|
def to_result(cx, *args)
|
|
self
|
|
end
|
|
|
|
def each
|
|
File.open(path, 'rb') do |file|
|
|
while buf = file.read(options[:buffer_size])
|
|
yield buf
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
protected
|
|
# Sends the file by streaming it 4096 bytes at a time. This way the
|
|
# whole file doesn't need to be read into memory at once. This makes
|
|
# it feasible to send even large files.
|
|
#
|
|
# Be careful to sanitize the path parameter if it coming from a web
|
|
# page. send_file(params[:path]) allows a malicious user to
|
|
# download any file on your server.
|
|
#
|
|
# Options:
|
|
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
|
# Defaults to File.basename(path).
|
|
# * <tt>:type</tt> - specifies an HTTP content type.
|
|
# Defaults to 'application/octet-stream'.
|
|
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
|
# Valid values are 'inline' and 'attachment' (default). When set to nil, the
|
|
# Content-Disposition and Content-Transfer-Encoding headers are omitted entirely.
|
|
# * <tt>:stream</tt> - whether to send the file to the user agent as it is read (true)
|
|
# or to read the entire file before sending (false). Defaults to true.
|
|
# * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.
|
|
# Defaults to 4096.
|
|
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
|
|
# * <tt>:last_modified</tt> - an optional RFC 2616 formatted date value (See Time#httpdate)
|
|
# indicating the last modified time of the file. If the request includes an
|
|
# If-Modified-Since header that matches this value exactly, a 304 Not Modified response
|
|
# is sent instead of the file. Defaults to the file's last modified
|
|
# time.
|
|
#
|
|
# The default Content-Type and Content-Disposition headers are
|
|
# set to download arbitrary binary files in as many browsers as
|
|
# possible. IE versions 4, 5, 5.5, and 6 are all known to have
|
|
# a variety of quirks (especially when downloading over SSL).
|
|
#
|
|
# Simple download:
|
|
# send_file '/path/to.zip'
|
|
#
|
|
# Show a JPEG in the browser:
|
|
# send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
|
|
#
|
|
# Show a 404 page in the browser:
|
|
# send_file '/path/to/404.html, :type => 'text/html; charset=utf-8', :status => 404
|
|
#
|
|
# Read about the other Content-* HTTP headers if you'd like to
|
|
# provide the user with more information (such as Content-Description).
|
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
|
|
#
|
|
# Also be aware that the document may be cached by proxies and browsers.
|
|
# The Pragma and Cache-Control headers declare how the file may be cached
|
|
# by intermediaries. They default to require clients to validate with
|
|
# the server before releasing cached responses. See
|
|
# http://www.mnot.net/cache_docs/ for an overview of web caching and
|
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
|
|
# for the Cache-Control header spec.
|
|
def send_file(path, options = {}) #:doc:
|
|
raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
|
|
|
|
options[:length] ||= File.size(path)
|
|
options[:filename] ||= File.basename(path)
|
|
options[:type] ||= Rack::File::MIME_TYPES[File.extname(options[:filename])[1..-1]] || 'text/plain'
|
|
options[:last_modified] ||= File.mtime(path).httpdate
|
|
send_file_headers! options
|
|
|
|
if options[:stream]
|
|
throw :halt, [options[:status] || 200, FileStreamer.new(path, options)]
|
|
else
|
|
File.open(path, 'rb') { |file| throw :halt, [options[:status] || 200, file.read] }
|
|
end
|
|
end
|
|
|
|
# Send binary data to the user as a file download. May set content type, apparent file name,
|
|
# and specify whether to show data inline or download as an attachment.
|
|
#
|
|
# Options:
|
|
# * <tt>:filename</tt> - Suggests a filename for the browser to use.
|
|
# * <tt>:type</tt> - specifies an HTTP content type.
|
|
# Defaults to 'application/octet-stream'.
|
|
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
|
# Valid values are 'inline' and 'attachment' (default).
|
|
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
|
|
# * <tt>:last_modified</tt> - an optional RFC 2616 formatted date value (See Time#httpdate)
|
|
# indicating the last modified time of the response entity. If the request includes an
|
|
# If-Modified-Since header that matches this value exactly, a 304 Not Modified response
|
|
# is sent instead of the data.
|
|
#
|
|
# Generic data download:
|
|
# send_data buffer
|
|
#
|
|
# Download a dynamically-generated tarball:
|
|
# send_data generate_tgz('dir'), :filename => 'dir.tgz'
|
|
#
|
|
# Display an image Active Record in the browser:
|
|
# send_data image.data, :type => image.content_type, :disposition => 'inline'
|
|
#
|
|
# See +send_file+ for more information on HTTP Content-* headers and caching.
|
|
def send_data(data, options = {}) #:doc:
|
|
send_file_headers! options.merge(:length => data.size)
|
|
throw :halt, [options[:status] || 200, data]
|
|
end
|
|
|
|
private
|
|
def send_file_headers!(options)
|
|
options = DEFAULT_SEND_FILE_OPTIONS.merge(options)
|
|
[:length, :type, :disposition].each do |arg|
|
|
raise ArgumentError, ":#{arg} option required" unless options.key?(arg)
|
|
end
|
|
|
|
# Send a "304 Not Modified" if the last_modified option is provided and matches
|
|
# the If-Modified-Since request header value.
|
|
if last_modified = options[:last_modified]
|
|
header 'Last-Modified' => last_modified
|
|
throw :halt, [ 304, '' ] if last_modified == request.env['HTTP_IF_MODIFIED_SINCE']
|
|
end
|
|
|
|
headers(
|
|
'Content-Length' => options[:length].to_s,
|
|
'Content-Type' => options[:type].strip # fixes a problem with extra '\r' with some browsers
|
|
)
|
|
|
|
# Omit Content-Disposition and Content-Transfer-Encoding headers if
|
|
# the :disposition option set to nil.
|
|
if !options[:disposition].nil?
|
|
disposition = options[:disposition].dup || 'attachment'
|
|
disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
|
|
headers 'Content-Disposition' => disposition, 'Content-Transfer-Encoding' => 'binary'
|
|
end
|
|
|
|
# Fix a problem with IE 6.0 on opening downloaded files:
|
|
# If Cache-Control: no-cache is set (which Rails does by default),
|
|
# IE removes the file it just downloaded from its cache immediately
|
|
# after it displays the "open/save" dialog, which means that if you
|
|
# hit "open" the file isn't there anymore when the application that
|
|
# is called for handling the download is run, so let's workaround that
|
|
header('Cache-Control' => 'private') if headers['Cache-Control'] == 'no-cache'
|
|
end
|
|
end
|
|
|
|
|
|
# Helper methods for building various aspects of the HTTP response.
|
|
module ResponseHelpers
|
|
|
|
# Immediately halt response execution by redirecting to the resource
|
|
# specified. The +path+ argument may be an absolute URL or a path
|
|
# relative to the site root. Additional arguments are passed to the
|
|
# halt.
|
|
#
|
|
# With no integer status code, a '302 Temporary Redirect' response is
|
|
# sent. To send a permanent redirect, pass an explicit status code of
|
|
# 301:
|
|
#
|
|
# redirect '/somewhere/else', 301
|
|
#
|
|
# NOTE: No attempt is made to rewrite the path based on application
|
|
# context. The 'Location' response header is set verbatim to the value
|
|
# provided.
|
|
def redirect(path, *args)
|
|
status(302)
|
|
header 'Location' => path
|
|
throw :halt, *args
|
|
end
|
|
|
|
# Access or modify response headers. With no argument, return the
|
|
# underlying headers Hash. With a Hash argument, add or overwrite
|
|
# existing response headers with the values provided:
|
|
#
|
|
# headers 'Content-Type' => "text/html;charset=utf-8",
|
|
# 'Last-Modified' => Time.now.httpdate,
|
|
# 'X-UA-Compatible' => 'IE=edge'
|
|
#
|
|
# This method also available in singular form (#header).
|
|
def headers(header = nil)
|
|
@response.headers.merge!(header) if header
|
|
@response.headers
|
|
end
|
|
alias :header :headers
|
|
|
|
# Set the content type of the response body (HTTP 'Content-Type' header).
|
|
#
|
|
# The +type+ argument may be an internet media type (e.g., 'text/html',
|
|
# 'application/xml+atom', 'image/png') or a Symbol key into the
|
|
# Rack::File::MIME_TYPES table.
|
|
#
|
|
# Media type parameters, such as "charset", may also be specified using the
|
|
# optional hash argument:
|
|
#
|
|
# get '/foo.html' do
|
|
# content_type 'text/html', :charset => 'utf-8'
|
|
# "<h1>Hello World</h1>"
|
|
# end
|
|
#
|
|
def content_type(type, params={})
|
|
type = Rack::File::MIME_TYPES[type.to_s] if type.kind_of?(Symbol)
|
|
fail "Invalid or undefined media_type: #{type}" if type.nil?
|
|
if params.any?
|
|
params = params.collect { |kv| "%s=%s" % kv }.join(', ')
|
|
type = [ type, params ].join(";")
|
|
end
|
|
response.header['Content-Type'] = type
|
|
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.
|
|
#
|
|
# Calling this method before perfoming heavy processing (e.g., lengthy
|
|
# database queries, template rendering, complex logic) can dramatically
|
|
# increase overall throughput with caching clients.
|
|
def last_modified(time)
|
|
time = time.to_time if time.respond_to?(:to_time)
|
|
time = time.httpdate if time.respond_to?(:httpdate)
|
|
response.header['Last-Modified'] = time
|
|
throw :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. For all other request
|
|
# methods, a '412 Precondition Failed' response is sent.
|
|
#
|
|
# Calling this method before perfoming heavy processing (e.g., lengthy
|
|
# database queries, template rendering, complex logic) can dramatically
|
|
# increase overall throughput with caching clients.
|
|
#
|
|
# === See Also
|
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19[RFC2616: ETag],
|
|
# ResponseHelpers#last_modified
|
|
def entity_tag(value, strength=:strong)
|
|
value =
|
|
case strength
|
|
when :strong then '"%s"' % value
|
|
when :weak then 'W/"%s"' % value
|
|
else raise TypeError, "strength must be one of :strong or :weak"
|
|
end
|
|
response.header['ETag'] = value
|
|
|
|
# Check for If-None-Match request header and halt if match is found.
|
|
etags = (request.env['HTTP_IF_NONE_MATCH'] || '').split(/\s*,\s*/)
|
|
if etags.include?(value) || etags.include?('*')
|
|
# GET/HEAD requests: send Not Modified response
|
|
throw :halt, 304 if request.get? || request.head?
|
|
# Other requests: send Precondition Failed response
|
|
throw :halt, 412
|
|
end
|
|
end
|
|
|
|
alias :etag :entity_tag
|
|
|
|
end
|
|
|
|
module RenderingHelpers
|
|
|
|
def render(renderer, template, options={})
|
|
m = method("render_#{renderer}")
|
|
result = m.call(resolve_template(renderer, template, options), options)
|
|
if layout = determine_layout(renderer, template, options)
|
|
result = m.call(resolve_template(renderer, layout, options), options) { result }
|
|
end
|
|
result
|
|
end
|
|
|
|
def determine_layout(renderer, template, options)
|
|
return if options[:layout] == false
|
|
layout_from_options = options[:layout] || :layout
|
|
resolve_template(renderer, layout_from_options, options, false)
|
|
end
|
|
|
|
private
|
|
|
|
def resolve_template(renderer, template, options, scream = true)
|
|
case template
|
|
when String
|
|
template
|
|
when Proc
|
|
template.call
|
|
when Symbol
|
|
if proc = templates[template]
|
|
resolve_template(renderer, proc, options, scream)
|
|
else
|
|
read_template_file(renderer, template, options, scream)
|
|
end
|
|
else
|
|
nil
|
|
end
|
|
end
|
|
|
|
def read_template_file(renderer, template, options, scream = true)
|
|
path = File.join(
|
|
options[:views_directory] || Sinatra.application.options.views,
|
|
"#{template}.#{renderer}"
|
|
)
|
|
unless File.exists?(path)
|
|
raise Errno::ENOENT.new(path) if scream
|
|
nil
|
|
else
|
|
File.read(path)
|
|
end
|
|
end
|
|
|
|
def templates
|
|
Sinatra.application.templates
|
|
end
|
|
|
|
end
|
|
|
|
module Erb
|
|
|
|
def erb(content, options={})
|
|
require 'erb'
|
|
render(:erb, content, options)
|
|
end
|
|
|
|
private
|
|
|
|
def render_erb(content, options = {})
|
|
::ERB.new(content).result(binding)
|
|
end
|
|
|
|
end
|
|
|
|
module Haml
|
|
|
|
def haml(content, options={})
|
|
require 'haml'
|
|
render(:haml, content, options)
|
|
end
|
|
|
|
private
|
|
|
|
def render_haml(content, options = {}, &b)
|
|
haml_options = (options[:options] || {}).merge(Sinatra.options.haml || {})
|
|
::Haml::Engine.new(content, haml_options).render(options[:scope] || self, options[:locals] || {}, &b)
|
|
end
|
|
|
|
end
|
|
|
|
# Generate valid CSS using Sass (part of Haml)
|
|
#
|
|
# Sass templates can be in external files with <tt>.sass</tt> extension or can use Sinatra's
|
|
# in_file_templates. In either case, the file can be rendered by passing the name of
|
|
# the template to the +sass+ method as a symbol.
|
|
#
|
|
# Unlike Haml, Sass does not support a layout file, so the +sass+ method will ignore both
|
|
# the default <tt>layout.sass</tt> file and any parameters passed in as <tt>:layout</tt> in
|
|
# the options hash.
|
|
#
|
|
# === Sass Template Files
|
|
#
|
|
# Sass templates can be stored in separate files with a <tt>.sass</tt>
|
|
# extension under the view path.
|
|
#
|
|
# Example:
|
|
# get '/stylesheet.css' do
|
|
# header 'Content-Type' => 'text/css; charset=utf-8'
|
|
# sass :stylesheet
|
|
# end
|
|
#
|
|
# The "views/stylesheet.sass" file might contain the following:
|
|
#
|
|
# body
|
|
# #admin
|
|
# :background-color #CCC
|
|
# #main
|
|
# :background-color #000
|
|
# #form
|
|
# :border-color #AAA
|
|
# :border-width 10px
|
|
#
|
|
# And yields the following output:
|
|
#
|
|
# body #admin {
|
|
# background-color: #CCC; }
|
|
# body #main {
|
|
# background-color: #000; }
|
|
#
|
|
# #form {
|
|
# border-color: #AAA;
|
|
# border-width: 10px; }
|
|
#
|
|
#
|
|
# NOTE: Haml must be installed or a LoadError will be raised the first time an
|
|
# attempt is made to render a Sass template.
|
|
#
|
|
# See http://haml.hamptoncatlin.com/docs/rdoc/classes/Sass.html for comprehensive documentation on Sass.
|
|
|
|
|
|
module Sass
|
|
|
|
def sass(content, options = {})
|
|
require 'sass'
|
|
|
|
# Sass doesn't support a layout, so we override any possible layout here
|
|
options[:layout] = false
|
|
|
|
render(:sass, content, options)
|
|
end
|
|
|
|
private
|
|
|
|
def render_sass(content, options = {})
|
|
::Sass::Engine.new(content).render
|
|
end
|
|
end
|
|
|
|
# Generating conservative XML content using Builder templates.
|
|
#
|
|
# Builder templates can be inline by passing a block to the builder method, or in
|
|
# external files with +.builder+ extension by passing the name of the template
|
|
# to the +builder+ method as a Symbol.
|
|
#
|
|
# === Inline Rendering
|
|
#
|
|
# If the builder method is given a block, the block is called directly with an
|
|
# +XmlMarkup+ instance and the result is returned as String:
|
|
# get '/who.xml' do
|
|
# builder do |xml|
|
|
# xml.instruct!
|
|
# xml.person do
|
|
# xml.name "Francis Albert Sinatra",
|
|
# :aka => "Frank Sinatra"
|
|
# xml.email 'frank@capitolrecords.com'
|
|
# end
|
|
# end
|
|
# end
|
|
#
|
|
# Yields the following XML:
|
|
# <?xml version='1.0' encoding='UTF-8'?>
|
|
# <person>
|
|
# <name aka='Frank Sinatra'>Francis Albert Sinatra</name>
|
|
# <email>Frank Sinatra</email>
|
|
# </person>
|
|
#
|
|
# === Builder Template Files
|
|
#
|
|
# Builder templates can be stored in separate files with a +.builder+
|
|
# extension under the view path. An +XmlMarkup+ object named +xml+ is automatically
|
|
# made available to template.
|
|
#
|
|
# Example:
|
|
# get '/bio.xml' do
|
|
# builder :bio
|
|
# end
|
|
#
|
|
# The "views/bio.builder" file might contain the following:
|
|
# xml.instruct! :xml, :version => '1.1'
|
|
# xml.person do
|
|
# xml.name "Francis Albert Sinatra"
|
|
# xml.aka "Frank Sinatra"
|
|
# xml.aka "Ol' Blue Eyes"
|
|
# xml.aka "The Chairman of the Board"
|
|
# xml.born 'date' => '1915-12-12' do
|
|
# xml.text! "Hoboken, New Jersey, U.S.A."
|
|
# end
|
|
# xml.died 'age' => 82
|
|
# end
|
|
#
|
|
# And yields the following output:
|
|
# <?xml version='1.1' encoding='UTF-8'?>
|
|
# <person>
|
|
# <name>Francis Albert Sinatra</name>
|
|
# <aka>Frank Sinatra</aka>
|
|
# <aka>Ol' Blue Eyes</aka>
|
|
# <aka>The Chairman of the Board</aka>
|
|
# <born date='1915-12-12'>Hoboken, New Jersey, U.S.A.</born>
|
|
# <died age='82' />
|
|
# </person>
|
|
#
|
|
# NOTE: Builder must be installed or a LoadError will be raised the first time an
|
|
# attempt is made to render a builder template.
|
|
#
|
|
# See http://builder.rubyforge.org/ for comprehensive documentation on Builder.
|
|
module Builder
|
|
|
|
def builder(content=nil, options={}, &block)
|
|
options, content = content, nil if content.is_a?(Hash)
|
|
content = Proc.new { block } if content.nil?
|
|
render(:builder, content, options)
|
|
end
|
|
|
|
private
|
|
|
|
def render_builder(content, options = {}, &b)
|
|
require 'builder'
|
|
xml = ::Builder::XmlMarkup.new(:indent => 2)
|
|
case content
|
|
when String
|
|
eval(content, binding, '<BUILDER>', 1)
|
|
when Proc
|
|
content.call(xml)
|
|
end
|
|
xml.target!
|
|
end
|
|
|
|
end
|
|
|
|
class EventContext
|
|
|
|
include ResponseHelpers
|
|
include Streaming
|
|
include RenderingHelpers
|
|
include Erb
|
|
include Haml
|
|
include Builder
|
|
include Sass
|
|
|
|
attr_accessor :request, :response
|
|
|
|
dslify_writer :status, :body
|
|
|
|
def initialize(request, response, route_params)
|
|
@request = request
|
|
@response = response
|
|
@route_params = route_params
|
|
@response.body = nil
|
|
end
|
|
|
|
def params
|
|
@params ||= begin
|
|
h = Hash.new {|h,k| h[k.to_s] if Symbol === k}
|
|
h.merge(@route_params.merge(@request.params))
|
|
end
|
|
end
|
|
|
|
def data
|
|
@data ||= params.keys.first
|
|
end
|
|
|
|
def stop(*args)
|
|
throw :halt, args
|
|
end
|
|
|
|
def complete(returned)
|
|
@response.body || returned
|
|
end
|
|
|
|
def session
|
|
request.env['rack.session'] ||= {}
|
|
end
|
|
|
|
private
|
|
|
|
def method_missing(name, *args, &b)
|
|
@response.send(name, *args, &b)
|
|
end
|
|
|
|
end
|
|
|
|
|
|
# The Application class represents the top-level working area of a
|
|
# Sinatra app. It provides the DSL for defining various aspects of the
|
|
# application and implements a Rack compatible interface for dispatching
|
|
# requests.
|
|
#
|
|
# Many of the instance methods defined in this class (#get, #post,
|
|
# #put, #delete, #layout, #before, #error, #not_found, etc.) are
|
|
# available at top-level scope. When invoked from top-level, the
|
|
# messages are forwarded to the "default application" (accessible
|
|
# at Sinatra::application).
|
|
class Application
|
|
|
|
# Hash of event handlers with request method keys and
|
|
# arrays of potential handlers as values.
|
|
attr_reader :events
|
|
|
|
# Hash of error handlers with error status codes as keys and
|
|
# handlers as values.
|
|
attr_reader :errors
|
|
|
|
# Hash of template name mappings.
|
|
attr_reader :templates
|
|
|
|
# Hash of filters with event name keys (:before) and arrays of
|
|
# handlers as values.
|
|
attr_reader :filters
|
|
|
|
# Array of objects to clear during reload. The objects in this array
|
|
# must respond to :clear.
|
|
attr_reader :clearables
|
|
|
|
# Object including open attribute methods for modifying Application
|
|
# configuration.
|
|
attr_reader :options
|
|
|
|
# List of methods available from top-level scope. When invoked from
|
|
# top-level the method is forwarded to the default application
|
|
# (Sinatra::application).
|
|
FORWARD_METHODS = %w[
|
|
get put post delete head template layout before error not_found
|
|
configures configure set set_options set_option enable disable use
|
|
]
|
|
|
|
# Create a new Application with a default configuration taken
|
|
# from the default_options Hash.
|
|
#
|
|
# NOTE: A default Application is automatically created the first
|
|
# time any of Sinatra's DSL related methods is invoked so there
|
|
# is typically no need to create an instance explicitly. See
|
|
# Sinatra::application for more information.
|
|
def initialize
|
|
@reloading = false
|
|
@clearables = [
|
|
@events = Hash.new { |hash, key| hash[key] = [] },
|
|
@errors = Hash.new,
|
|
@filters = Hash.new { |hash, key| hash[key] = [] },
|
|
@templates = Hash.new,
|
|
@middleware = []
|
|
]
|
|
@options = OpenStruct.new(self.class.default_options)
|
|
load_default_configuration!
|
|
end
|
|
|
|
# Hash of default application configuration options. When a new
|
|
# Application is created, the #options object takes its initial values
|
|
# from here.
|
|
#
|
|
# Changes to the default_options Hash effect only Application objects
|
|
# created after the changes are made. For this reason, modifications to
|
|
# the default_options Hash typically occur at the very beginning of a
|
|
# file, before any DSL related functions are invoked.
|
|
def self.default_options
|
|
return @default_options unless @default_options.nil?
|
|
root = File.expand_path(File.dirname($0))
|
|
@default_options = {
|
|
:run => true,
|
|
:port => 4567,
|
|
:env => :development,
|
|
:root => root,
|
|
:views => root + '/views',
|
|
:public => root + '/public',
|
|
:sessions => false,
|
|
:logging => true,
|
|
:raise_errors => false
|
|
}
|
|
load_default_options_from_command_line!
|
|
@default_options
|
|
end
|
|
|
|
# Search ARGV for command line arguments and update the
|
|
# Sinatra::default_options Hash accordingly. This method is
|
|
# invoked the first time the default_options Hash is accessed.
|
|
# NOTE: Ignores --name so unit/spec tests can run individually
|
|
def self.load_default_options_from_command_line! #:nodoc:
|
|
require 'optparse'
|
|
OptionParser.new do |op|
|
|
op.on('-p port') { |port| default_options[:port] = port }
|
|
op.on('-e env') { |env| default_options[:env] = env }
|
|
op.on('-x') { default_options[:mutex] = true }
|
|
op.on('-s server') { |server| default_options[:server] = server }
|
|
end.parse!(ARGV.dup.select { |o| o !~ /--name/ })
|
|
end
|
|
|
|
# Determine whether the application is in the process of being
|
|
# reloaded.
|
|
def reloading?
|
|
@reloading == true
|
|
end
|
|
|
|
# Yield to the block for configuration if the current environment
|
|
# matches any included in the +envs+ list. Always yield to the block
|
|
# when no environment is specified.
|
|
#
|
|
# NOTE: configuration blocks are not executed during reloads.
|
|
def configures(*envs, &b)
|
|
return if reloading?
|
|
yield self if envs.empty? || envs.include?(options.env)
|
|
end
|
|
|
|
alias :configure :configures
|
|
|
|
# When both +option+ and +value+ arguments are provided, set the option
|
|
# specified. With a single Hash argument, set all options specified in
|
|
# Hash. Options are available via the Application#options object.
|
|
#
|
|
# Setting individual options:
|
|
# set :port, 80
|
|
# set :env, :production
|
|
# set :views, '/path/to/views'
|
|
#
|
|
# Setting multiple options:
|
|
# set :port => 80,
|
|
# :env => :production,
|
|
# :views => '/path/to/views'
|
|
#
|
|
def set(option, value=self)
|
|
if value == self && option.kind_of?(Hash)
|
|
option.each { |key,val| set(key, val) }
|
|
else
|
|
options.send("#{option}=", value)
|
|
end
|
|
end
|
|
|
|
alias :set_option :set
|
|
alias :set_options :set
|
|
|
|
# Enable the options specified by setting their values to true. For
|
|
# example, to enable sessions and logging:
|
|
# enable :sessions, :logging
|
|
def enable(*opts)
|
|
opts.each { |key| set(key, true) }
|
|
end
|
|
|
|
# Disable the options specified by setting their values to false. For
|
|
# example, to disable logging and automatic run:
|
|
# disable :logging, :run
|
|
def disable(*opts)
|
|
opts.each { |key| set(key, false) }
|
|
end
|
|
|
|
# Define an event handler for the given request method and path
|
|
# spec. The block is executed when a request matches the method
|
|
# and spec.
|
|
#
|
|
# NOTE: The #get, #post, #put, and #delete helper methods should
|
|
# be used to define events when possible.
|
|
def event(method, path, options = {}, &b)
|
|
events[method].push(Event.new(path, options, &b)).last
|
|
end
|
|
|
|
# Define an event handler for GET requests.
|
|
def get(path, options={}, &b)
|
|
event(:get, path, options, &b)
|
|
end
|
|
|
|
# Define an event handler for POST requests.
|
|
def post(path, options={}, &b)
|
|
event(:post, path, options, &b)
|
|
end
|
|
|
|
# Define an event handler for HEAD requests.
|
|
def head(path, options={}, &b)
|
|
event(:head, path, options, &b)
|
|
end
|
|
|
|
# Define an event handler for PUT requests.
|
|
#
|
|
# NOTE: PUT events are triggered when the HTTP request method is
|
|
# PUT and also when the request method is POST and the body includes a
|
|
# "_method" parameter set to "PUT".
|
|
def put(path, options={}, &b)
|
|
event(:put, path, options, &b)
|
|
end
|
|
|
|
# Define an event handler for DELETE requests.
|
|
#
|
|
# NOTE: DELETE events are triggered when the HTTP request method is
|
|
# DELETE and also when the request method is POST and the body includes a
|
|
# "_method" parameter set to "DELETE".
|
|
def delete(path, options={}, &b)
|
|
event(:delete, path, options, &b)
|
|
end
|
|
|
|
# Visits and invokes each handler registered for the +request_method+ in
|
|
# definition order until a Result response is produced. If no handler
|
|
# responds with a Result, the NotFound error handler is invoked.
|
|
#
|
|
# When the request_method is "HEAD" and no valid Result is produced by
|
|
# the set of handlers registered for HEAD requests, an attempt is made to
|
|
# invoke the GET handlers to generate the response before resorting to the
|
|
# default error handler.
|
|
def lookup(request)
|
|
method = request.request_method.downcase.to_sym
|
|
events[method].eject(&[:invoke, request]) ||
|
|
(events[:get].eject(&[:invoke, request]) if method == :head) ||
|
|
errors[NotFound].invoke(request)
|
|
end
|
|
|
|
|
|
# Define a named template. The template may be referenced from
|
|
# event handlers by passing the name as a Symbol to rendering
|
|
# methods. The block is executed each time the template is rendered
|
|
# and the resulting object is passed to the template handler.
|
|
#
|
|
# The following example defines a HAML template named hello and
|
|
# invokes it from an event handler:
|
|
#
|
|
# template :hello do
|
|
# "h1 Hello World!"
|
|
# end
|
|
#
|
|
# get '/' do
|
|
# haml :hello
|
|
# end
|
|
#
|
|
def template(name, &b)
|
|
templates[name] = b
|
|
end
|
|
|
|
# Define a layout template.
|
|
def layout(name=:layout, &b)
|
|
template(name, &b)
|
|
end
|
|
|
|
# Define a custom error handler for the exception class +type+. The block
|
|
# is invoked when the specified exception type is raised from an error
|
|
# handler and can manipulate the response as needed:
|
|
#
|
|
# error MyCustomError do
|
|
# status 500
|
|
# 'So what happened was...' + request.env['sinatra.error'].message
|
|
# end
|
|
#
|
|
# The Sinatra::ServerError handler is used by default when an exception
|
|
# occurs and no matching error handler is found.
|
|
def error(type=ServerError, options = {}, &b)
|
|
errors[type] = Error.new(type, &b)
|
|
end
|
|
|
|
# Define a custom error handler for '404 Not Found' responses. This is a
|
|
# shorthand for:
|
|
# error NotFound do
|
|
# ..
|
|
# end
|
|
def not_found(options={}, &b)
|
|
error NotFound, options, &b
|
|
end
|
|
|
|
# Define a request filter. When +type+ is +:before+, execute the block
|
|
# in the context of each request before matching event handlers.
|
|
def filter(type, &b)
|
|
filters[type] << b
|
|
end
|
|
|
|
# Invoke the block in the context of each request before invoking
|
|
# matching event handlers.
|
|
def before(&b)
|
|
filter :before, &b
|
|
end
|
|
|
|
def development?
|
|
options.env == :development
|
|
end
|
|
|
|
# Clear all events, templates, filters, and error handlers
|
|
# and then reload the application source file. This occurs
|
|
# automatically before each request is processed in development.
|
|
def reload!
|
|
clearables.each(&:clear)
|
|
load_default_configuration!
|
|
@pipeline = nil
|
|
@reloading = true
|
|
Kernel.load $0
|
|
@reloading = false
|
|
end
|
|
|
|
# Determine whether the application is in the process of being
|
|
# reloaded.
|
|
def reloading?
|
|
@reloading == true
|
|
end
|
|
|
|
# Mutex instance used for thread synchronization.
|
|
def mutex
|
|
@@mutex ||= Mutex.new
|
|
end
|
|
|
|
# Yield to the block with thread synchronization
|
|
def run_safely
|
|
if options.mutex
|
|
mutex.synchronize { yield }
|
|
else
|
|
yield
|
|
end
|
|
end
|
|
|
|
# Add a piece of Rack middleware to the pipeline leading to the
|
|
# application.
|
|
def use(klass, *args, &block)
|
|
fail "#{klass} must respond to 'new'" unless klass.respond_to?(:new)
|
|
@pipeline = nil
|
|
@middleware.push([ klass, args, block ]).last
|
|
end
|
|
|
|
private
|
|
|
|
# Rack middleware derived from current state of application options.
|
|
# These components are plumbed in at the very beginning of the
|
|
# pipeline.
|
|
def optional_middleware
|
|
[
|
|
([ Rack::CommonLogger, [], nil ] if options.logging),
|
|
([ Rack::Session::Cookie, [], nil ] if options.sessions)
|
|
].compact
|
|
end
|
|
|
|
# Rack middleware explicitly added to the application with #use. These
|
|
# components are plumbed into the pipeline downstream from
|
|
# #optional_middle.
|
|
def explicit_middleware
|
|
@middleware
|
|
end
|
|
|
|
# All Rack middleware used to construct the pipeline.
|
|
def middleware
|
|
optional_middleware + explicit_middleware
|
|
end
|
|
|
|
public
|
|
|
|
# An assembled pipeline of Rack middleware that leads eventually to
|
|
# the Application#invoke method. The pipeline is built upon first
|
|
# access. Defining new middleware with Application#use or manipulating
|
|
# application options may cause the pipeline to be rebuilt.
|
|
def pipeline
|
|
@pipeline ||=
|
|
middleware.inject(method(:dispatch)) do |app,(klass,args,block)|
|
|
klass.new(app, *args, &block)
|
|
end
|
|
end
|
|
|
|
# Rack compatible request invocation interface.
|
|
def call(env)
|
|
reload! if development?
|
|
pipeline.call(env)
|
|
end
|
|
|
|
# Request invocation handler - called at the end of the Rack pipeline
|
|
# for each request.
|
|
#
|
|
# 1. Create Rack::Request, Rack::Response helper objects.
|
|
# 2. Lookup event handler based on request method and path.
|
|
# 3. Create new EventContext to house event handler evaluation.
|
|
# 4. Invoke each #before filter in context of EventContext object.
|
|
# 5. Invoke event handler in context of EventContext object.
|
|
# 6. Return response to Rack.
|
|
#
|
|
# See the Rack specification for detailed information on the
|
|
# +env+ argument and return value.
|
|
def dispatch(env)
|
|
request = Rack::Request.new(env)
|
|
result = lookup(request)
|
|
context = EventContext.new(request, Rack::Response.new, result.params)
|
|
context.status(result.status)
|
|
begin
|
|
returned = run_safely do
|
|
catch(:halt) do
|
|
filters[:before].each { |f| context.instance_eval(&f) }
|
|
[:complete, context.instance_eval(&result.block)]
|
|
end
|
|
end
|
|
body = returned.to_result(context)
|
|
rescue => e
|
|
request.env['sinatra.error'] = e
|
|
context.status(500)
|
|
result = (errors[e.class] || errors[ServerError]).invoke(request)
|
|
returned = run_safely do
|
|
catch(:halt) do
|
|
[:complete, context.instance_eval(&result.block)]
|
|
end
|
|
end
|
|
body = returned.to_result(context)
|
|
end
|
|
body = '' unless body.respond_to?(:each)
|
|
body = '' if request.request_method.upcase == 'HEAD'
|
|
context.body = body.kind_of?(String) ? [*body] : body
|
|
context.finish
|
|
end
|
|
|
|
# Called immediately after the application is initialized or reloaded to
|
|
# register default events, templates, and error handlers.
|
|
def load_default_configuration!
|
|
|
|
# The static event is always executed first.
|
|
events[:get] << Static.new
|
|
|
|
# Default configuration for all environments.
|
|
configure do
|
|
error do
|
|
raise request.env['sinatra.error'] if Sinatra.options.raise_errors
|
|
'<h1>Internal Server Error</h1>'
|
|
end
|
|
not_found { '<h1>Not Found</h1>'}
|
|
end
|
|
|
|
configures :development do
|
|
|
|
get '/sinatra_custom_images/:image.png' do
|
|
File.read(File.dirname(__FILE__) + "/../images/#{params[:image]}.png")
|
|
end
|
|
|
|
not_found do
|
|
%Q(
|
|
<style>
|
|
body {
|
|
text-align: center;
|
|
color: #888;
|
|
font-family: Arial;
|
|
font-size: 22px;
|
|
margin: 20px;
|
|
}
|
|
#content {
|
|
margin: 0 auto;
|
|
width: 500px;
|
|
text-align: left;
|
|
}
|
|
</style>
|
|
<html>
|
|
<body>
|
|
<h2>Sinatra doesn't know this diddy.</h2>
|
|
<img src='/sinatra_custom_images/404.png'></img>
|
|
<div id="content">
|
|
Try this:
|
|
<pre>#{request.request_method.downcase} "#{request.path_info}" do
|
|
.. do something ..
|
|
end<pre>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
)
|
|
end
|
|
|
|
error do
|
|
@error = request.env['sinatra.error']
|
|
%Q(
|
|
<html>
|
|
<body>
|
|
<style type="text/css" media="screen">
|
|
body {
|
|
font-family: Verdana;
|
|
color: #333;
|
|
}
|
|
|
|
#content {
|
|
width: 700px;
|
|
margin-left: 20px;
|
|
}
|
|
|
|
#content h1 {
|
|
width: 99%;
|
|
color: #1D6B8D;
|
|
font-weight: bold;
|
|
}
|
|
|
|
#stacktrace {
|
|
margin-top: -20px;
|
|
}
|
|
|
|
#stacktrace pre {
|
|
font-size: 12px;
|
|
border-left: 2px solid #ddd;
|
|
padding-left: 10px;
|
|
}
|
|
|
|
#stacktrace img {
|
|
margin-top: 10px;
|
|
}
|
|
</style>
|
|
<div id="content">
|
|
<img src="/sinatra_custom_images/500.png" />
|
|
<div class="info">
|
|
Params: <pre>#{params.inspect}
|
|
</div>
|
|
<div id="stacktrace">
|
|
<h1>#{Rack::Utils.escape_html(@error.class.name + ' - ' + @error.message)}</h1>
|
|
<pre><code>#{Rack::Utils.escape_html(@error.backtrace.join("\n"))}</code></pre>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
private :load_default_configuration!
|
|
|
|
end
|
|
|
|
end
|
|
|
|
# Delegate DSLish methods to the currently active Sinatra::Application
|
|
# instance.
|
|
Sinatra::Application::FORWARD_METHODS.each do |method|
|
|
eval(<<-EOS, binding, '(__DSL__)', 1)
|
|
def #{method}(*args, &b)
|
|
Sinatra.application.#{method}(*args, &b)
|
|
end
|
|
EOS
|
|
end
|
|
|
|
def helpers(&b)
|
|
Sinatra::EventContext.class_eval(&b)
|
|
end
|
|
|
|
def use_in_file_templates!
|
|
require 'stringio'
|
|
templates = IO.read(caller.first.split(':').first).split('__FILE__').last
|
|
data = StringIO.new(templates)
|
|
current_template = nil
|
|
data.each do |line|
|
|
if line =~ /^@@\s?(.*)/
|
|
current_template = $1.to_sym
|
|
Sinatra.application.templates[current_template] = ''
|
|
elsif current_template
|
|
Sinatra.application.templates[current_template] << line
|
|
end
|
|
end
|
|
end
|
|
|
|
def mime(ext, type)
|
|
Rack::File::MIME_TYPES[ext.to_s] = type
|
|
end
|
|
|
|
### Misc Core Extensions
|
|
|
|
module Kernel
|
|
|
|
def silence_warnings
|
|
old_verbose, $VERBOSE = $VERBOSE, nil
|
|
yield
|
|
ensure
|
|
$VERBOSE = old_verbose
|
|
end
|
|
|
|
end
|
|
|
|
class String
|
|
|
|
# Converts +self+ to an escaped URI parameter value
|
|
# 'Foo Bar'.to_param # => 'Foo%20Bar'
|
|
def to_param
|
|
Rack::Utils.escape(self)
|
|
end
|
|
alias :http_escape :to_param
|
|
|
|
# Converts +self+ from an escaped URI parameter value
|
|
# 'Foo%20Bar'.from_param # => 'Foo Bar'
|
|
def from_param
|
|
Rack::Utils.unescape(self)
|
|
end
|
|
alias :http_unescape :from_param
|
|
|
|
end
|
|
|
|
class Hash
|
|
|
|
def to_params
|
|
map { |k,v| "#{k}=#{URI.escape(v)}" }.join('&')
|
|
end
|
|
|
|
def symbolize_keys
|
|
self.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
|
|
end
|
|
|
|
def pass(*keys)
|
|
reject { |k,v| !keys.include?(k) }
|
|
end
|
|
|
|
end
|
|
|
|
class Symbol
|
|
|
|
def to_proc
|
|
Proc.new { |*args| args.shift.__send__(self, *args) }
|
|
end
|
|
|
|
end
|
|
|
|
class Array
|
|
|
|
def to_hash
|
|
self.inject({}) { |h, (k, v)| h[k] = v; h }
|
|
end
|
|
|
|
def to_proc
|
|
Proc.new { |*args| args.shift.__send__(self[0], *(args + self[1..-1])) }
|
|
end
|
|
|
|
end
|
|
|
|
module Enumerable
|
|
|
|
def eject(&block)
|
|
find { |e| result = block[e] and break result }
|
|
end
|
|
|
|
end
|
|
|
|
### Core Extension results for throw :halt
|
|
|
|
class Proc
|
|
def to_result(cx, *args)
|
|
cx.instance_eval(&self)
|
|
args.shift.to_result(cx, *args)
|
|
end
|
|
end
|
|
|
|
class String
|
|
def to_result(cx, *args)
|
|
args.shift.to_result(cx, *args)
|
|
self
|
|
end
|
|
end
|
|
|
|
class Array
|
|
def to_result(cx, *args)
|
|
self.shift.to_result(cx, *self)
|
|
end
|
|
end
|
|
|
|
class Symbol
|
|
def to_result(cx, *args)
|
|
cx.send(self, *args)
|
|
end
|
|
end
|
|
|
|
class Fixnum
|
|
def to_result(cx, *args)
|
|
cx.status self
|
|
args.shift.to_result(cx, *args)
|
|
end
|
|
end
|
|
|
|
class NilClass
|
|
def to_result(cx, *args)
|
|
''
|
|
end
|
|
end
|
|
|
|
at_exit do
|
|
raise $! if $!
|
|
if Sinatra.application.options.run
|
|
Sinatra.run
|
|
end
|
|
end
|
|
|
|
mime :xml, 'application/xml'
|
|
mime :js, 'application/javascript'
|