mirror of
				https://github.com/KevinMidboe/linguist.git
				synced 2025-10-29 17:50:22 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1776 lines
		
	
	
		
			57 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			1776 lines
		
	
	
		
			57 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| # external dependencies
 | |
| require 'rack'
 | |
| require 'tilt'
 | |
| require "rack/protection"
 | |
| 
 | |
| # stdlib dependencies
 | |
| require 'thread'
 | |
| require 'time'
 | |
| require 'uri'
 | |
| 
 | |
| # other files we need
 | |
| require 'sinatra/showexceptions'
 | |
| require 'sinatra/version'
 | |
| 
 | |
| module Sinatra
 | |
|   # The request object. See Rack::Request for more info:
 | |
|   # http://rack.rubyforge.org/doc/classes/Rack/Request.html
 | |
|   class Request < Rack::Request
 | |
|     # Returns an array of acceptable media types for the response
 | |
|     def accept
 | |
|       @env['sinatra.accept'] ||= begin
 | |
|         entries = @env['HTTP_ACCEPT'].to_s.split(',')
 | |
|         entries.map { |e| accept_entry(e) }.sort_by(&:last).map(&:first)
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     def preferred_type(*types)
 | |
|       return accept.first if types.empty?
 | |
|       types.flatten!
 | |
|       accept.detect do |pattern|
 | |
|         type = types.detect { |t| File.fnmatch(pattern, t) }
 | |
|         return type if type
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     alias accept? preferred_type
 | |
|     alias secure? ssl?
 | |
| 
 | |
|     def forwarded?
 | |
|       @env.include? "HTTP_X_FORWARDED_HOST"
 | |
|     end
 | |
| 
 | |
|     def safe?
 | |
|       get? or head? or options? or trace?
 | |
|     end
 | |
| 
 | |
|     def idempotent?
 | |
|       safe? or put? or delete?
 | |
|     end
 | |
| 
 | |
|     private
 | |
| 
 | |
|     def accept_entry(entry)
 | |
|       type, *options = entry.delete(' ').split(';')
 | |
|       quality = 0 # we sort smallest first
 | |
|       options.delete_if { |e| quality = 1 - e[2..-1].to_f if e.start_with? 'q=' }
 | |
|       [type, [quality, type.count('*'), 1 - options.size]]
 | |
|     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 body=(value)
 | |
|       value = value.body while Rack::Response === value
 | |
|       @body = String === value ? [value.to_str] : value
 | |
|     end
 | |
| 
 | |
|     def each
 | |
|       block_given? ? super : enum_for(:each)
 | |
|     end
 | |
| 
 | |
|     def finish
 | |
|       if status.to_i / 100 == 1
 | |
|         headers.delete "Content-Length"
 | |
|         headers.delete "Content-Type"
 | |
|       elsif Array === body and not [204, 304].include?(status.to_i)
 | |
|         # if some other code has already set Content-Length, don't muck with it
 | |
|         # currently, this would be the static file-handler
 | |
|         headers["Content-Length"] ||= body.inject(0) { |l, p| l + Rack::Utils.bytesize(p) }.to_s
 | |
|       end
 | |
| 
 | |
|       # Rack::Response#finish sometimes returns self as response body. We don't want that.
 | |
|       status, headers, result = super
 | |
|       result = body if result == self
 | |
|       [status, headers, result]
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   # Some Rack handlers (Thin, Rainbows!) implement an extended body object protocol, however,
 | |
|   # some middleware (namely Rack::Lint) will break it by not mirroring the methods in question.
 | |
|   # This middleware will detect an extended body object and will make sure it reaches the
 | |
|   # handler directly. We do this here, so our middleware and middleware set up by the app will
 | |
|   # still be able to run.
 | |
|   class ExtendedRack < Struct.new(:app)
 | |
|     def call(env)
 | |
|       result, callback = app.call(env), env['async.callback']
 | |
|       return result unless callback and async?(*result)
 | |
|       after_response { callback.call result }
 | |
|       setup_close(env, *result)
 | |
|       throw :async
 | |
|     end
 | |
| 
 | |
|     private
 | |
| 
 | |
|     def setup_close(env, status, header, body)
 | |
|       return unless body.respond_to? :close and env.include? 'async.close'
 | |
|       env['async.close'].callback { body.close }
 | |
|       env['async.close'].errback { body.close }
 | |
|     end
 | |
| 
 | |
|     def after_response(&block)
 | |
|       raise NotImplementedError, "only supports EventMachine at the moment" unless defined? EventMachine
 | |
|       EventMachine.next_tick(&block)
 | |
|     end
 | |
| 
 | |
|     def async?(status, headers, body)
 | |
|       return true if status == -1
 | |
|       body.respond_to? :callback and body.respond_to? :errback
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   # Behaves exactly like Rack::CommonLogger with the notable exception that it does nothing,
 | |
|   # if another CommonLogger is already in the middleware chane.
 | |
|   class CommonLogger < Rack::CommonLogger
 | |
|     def call(env)
 | |
|       env['sinatra.commonlogger'] ? @app.call(env) : super
 | |
|     end
 | |
| 
 | |
|     superclass.class_eval do
 | |
|       alias call_without_check call unless method_defined? :call_without_check
 | |
|       def call(env)
 | |
|         env['sinatra.commonlogger'] = true
 | |
|         call_without_check(env)
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   class NotFound < NameError #:nodoc:
 | |
|     def http_status; 404 end
 | |
|   end
 | |
| 
 | |
|   # Methods available to routes, before/after 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
 | |
|       elsif value
 | |
|         response.body = value
 | |
|       else
 | |
|         response.body
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Halt processing and redirect to the URI provided.
 | |
|     def redirect(uri, *args)
 | |
|       if env['HTTP_VERSION'] == 'HTTP/1.1' and env["REQUEST_METHOD"] != 'GET'
 | |
|         status 303
 | |
|       else
 | |
|         status 302
 | |
|       end
 | |
| 
 | |
|       # According to RFC 2616 section 14.30, "the field value consists of a
 | |
|       # single absolute URI"
 | |
|       response['Location'] = uri(uri, settings.absolute_redirects?, settings.prefixed_redirects?)
 | |
|       halt(*args)
 | |
|     end
 | |
| 
 | |
|     # Generates the absolute URI for a given path in the app.
 | |
|     # Takes Rack routers and reverse proxies into account.
 | |
|     def uri(addr = nil, absolute = true, add_script_name = true)
 | |
|       return addr if addr =~ /\A[A-z][A-z0-9\+\.\-]*:/
 | |
|       uri = [host = ""]
 | |
|       if absolute
 | |
|         host << "http#{'s' if request.secure?}://"
 | |
|         if request.forwarded? or request.port != (request.secure? ? 443 : 80)
 | |
|           host << request.host_with_port
 | |
|         else
 | |
|           host << request.host
 | |
|         end
 | |
|       end
 | |
|       uri << request.script_name.to_s if add_script_name
 | |
|       uri << (addr ? addr : request.path_info).to_s
 | |
|       File.join uri
 | |
|     end
 | |
| 
 | |
|     alias url uri
 | |
|     alias to uri
 | |
| 
 | |
|     # 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
 | |
|       request.session
 | |
|     end
 | |
| 
 | |
|     # Access shared logger object.
 | |
|     def logger
 | |
|       request.logger
 | |
|     end
 | |
| 
 | |
|     # Look up a media type by file extension in Rack's mime registry.
 | |
|     def mime_type(type)
 | |
|       Base.mime_type(type)
 | |
|     end
 | |
| 
 | |
|     # Set the Content-Type of the response body given a media type or file
 | |
|     # extension.
 | |
|     def content_type(type = nil, params={})
 | |
|       return response['Content-Type'] unless type
 | |
|       default = params.delete :default
 | |
|       mime_type = mime_type(type) || default
 | |
|       fail "Unknown media type: %p" % type if mime_type.nil?
 | |
|       mime_type = mime_type.dup
 | |
|       unless params.include? :charset or settings.add_charset.all? { |p| not p === mime_type }
 | |
|         params[:charset] = params.delete('charset') || settings.default_encoding
 | |
|       end
 | |
|       params.delete :charset if mime_type.include? 'charset'
 | |
|       unless params.empty?
 | |
|         mime_type << (mime_type.include?(';') ? ', ' : ';')
 | |
|         mime_type << params.map { |kv| kv.join('=') }.join(', ')
 | |
|       end
 | |
|       response['Content-Type'] = mime_type
 | |
|     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
 | |
|         ext = File.extname(filename)
 | |
|         content_type(ext) unless response['Content-Type'] or ext.empty?
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Use the contents of the file at +path+ as the response body.
 | |
|     def send_file(path, opts={})
 | |
|       if opts[:type] or not response['Content-Type']
 | |
|         content_type opts[:type] || File.extname(path), :default => 'application/octet-stream'
 | |
|       end
 | |
| 
 | |
|       if opts[:disposition] == 'attachment' || opts[:filename]
 | |
|         attachment opts[:filename] || path
 | |
|       elsif opts[:disposition] == 'inline'
 | |
|         response['Content-Disposition'] = 'inline'
 | |
|       end
 | |
| 
 | |
|       last_modified opts[:last_modified] if opts[:last_modified]
 | |
| 
 | |
|       file      = Rack::File.new nil
 | |
|       file.path = path
 | |
|       result    = file.serving env
 | |
|       result[1].each { |k,v| headers[k] ||= v }
 | |
|       headers['Content-Length'] = result[1]['Content-Length']
 | |
|       halt opts[:status] || result[0], result[2]
 | |
|     rescue Errno::ENOENT
 | |
|       not_found
 | |
|     end
 | |
| 
 | |
|     # Class of the response body in case you use #stream.
 | |
|     #
 | |
|     # Three things really matter: The front and back block (back being the
 | |
|     # blog generating content, front the one sending it to the client) and
 | |
|     # the scheduler, integrating with whatever concurrency feature the Rack
 | |
|     # handler is using.
 | |
|     #
 | |
|     # Scheduler has to respond to defer and schedule.
 | |
|     class Stream
 | |
|       def self.schedule(*) yield end
 | |
|       def self.defer(*)    yield end
 | |
| 
 | |
|       def initialize(scheduler = self.class, keep_open = false, &back)
 | |
|         @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open
 | |
|         @callbacks, @closed = [], false
 | |
|       end
 | |
| 
 | |
|       def close
 | |
|         return if @closed
 | |
|         @closed = true
 | |
|         @scheduler.schedule { @callbacks.each { |c| c.call }}
 | |
|       end
 | |
| 
 | |
|       def each(&front)
 | |
|         @front = front
 | |
|         @scheduler.defer do
 | |
|           begin
 | |
|             @back.call(self)
 | |
|           rescue Exception => e
 | |
|             @scheduler.schedule { raise e }
 | |
|           end
 | |
|           close unless @keep_open
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       def <<(data)
 | |
|         @scheduler.schedule { @front.call(data.to_s) }
 | |
|         self
 | |
|       end
 | |
| 
 | |
|       def callback(&block)
 | |
|         return yield if @closed
 | |
|         @callbacks << block
 | |
|       end
 | |
| 
 | |
|       alias errback callback
 | |
|     end
 | |
| 
 | |
|     # Allows to start sending data to the client even though later parts of
 | |
|     # the response body have not yet been generated.
 | |
|     #
 | |
|     # The close parameter specifies whether Stream#close should be called
 | |
|     # after the block has been executed. This is only relevant for evented
 | |
|     # servers like Thin or Rainbows.
 | |
|     def stream(keep_open = false)
 | |
|       scheduler = env['async.callback'] ? EventMachine : Stream
 | |
|       current   = @params.dup
 | |
|       body Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
 | |
|     end
 | |
| 
 | |
|     # Specify response freshness policy for HTTP caches (Cache-Control header).
 | |
|     # Any number of non-value directives (:public, :private, :no_cache,
 | |
|     # :no_store, :must_revalidate, :proxy_revalidate) may be passed along with
 | |
|     # a Hash of value directives (:max_age, :min_stale, :s_max_age).
 | |
|     #
 | |
|     #   cache_control :public, :must_revalidate, :max_age => 60
 | |
|     #   => Cache-Control: public, must-revalidate, max-age=60
 | |
|     #
 | |
|     # See RFC 2616 / 14.9 for more on standard cache control directives:
 | |
|     # http://tools.ietf.org/html/rfc2616#section-14.9.1
 | |
|     def cache_control(*values)
 | |
|       if values.last.kind_of?(Hash)
 | |
|         hash = values.pop
 | |
|         hash.reject! { |k,v| v == false }
 | |
|         hash.reject! { |k,v| values << k if v == true }
 | |
|       else
 | |
|         hash = {}
 | |
|       end
 | |
| 
 | |
|       values.map! { |value| value.to_s.tr('_','-') }
 | |
|       hash.each do |key, value|
 | |
|         key = key.to_s.tr('_', '-')
 | |
|         value = value.to_i if key == "max-age"
 | |
|         values << [key, value].join('=')
 | |
|       end
 | |
| 
 | |
|       response['Cache-Control'] = values.join(', ') if values.any?
 | |
|     end
 | |
| 
 | |
|     # Set the Expires header and Cache-Control/max-age directive. Amount
 | |
|     # can be an integer number of seconds in the future or a Time object
 | |
|     # indicating when the response should be considered "stale". The remaining
 | |
|     # "values" arguments are passed to the #cache_control helper:
 | |
|     #
 | |
|     #   expires 500, :public, :must_revalidate
 | |
|     #   => Cache-Control: public, must-revalidate, max-age=60
 | |
|     #   => Expires: Mon, 08 Jun 2009 08:50:17 GMT
 | |
|     #
 | |
|     def expires(amount, *values)
 | |
|       values << {} unless values.last.kind_of?(Hash)
 | |
| 
 | |
|       if amount.is_a? Integer
 | |
|         time    = Time.now + amount.to_i
 | |
|         max_age = amount
 | |
|       else
 | |
|         time    = time_for amount
 | |
|         max_age = time - Time.now
 | |
|       end
 | |
| 
 | |
|       values.last.merge!(:max_age => max_age)
 | |
|       cache_control(*values)
 | |
| 
 | |
|       response['Expires'] = time.httpdate
 | |
|     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 is
 | |
|     # equal or later than the time specified, execution is immediately halted
 | |
|     # with a '304 Not Modified' response.
 | |
|     def last_modified(time)
 | |
|       return unless time
 | |
|       time = time_for time
 | |
|       response['Last-Modified'] = time.httpdate
 | |
|       return if env['HTTP_IF_NONE_MATCH']
 | |
| 
 | |
|       if status == 200 and env['HTTP_IF_MODIFIED_SINCE']
 | |
|         # compare based on seconds since epoch
 | |
|         since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
 | |
|         halt 304 if since >= time.to_i
 | |
|       end
 | |
| 
 | |
|       if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE']
 | |
|         # compare based on seconds since epoch
 | |
|         since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
 | |
|         halt 412 if since < time.to_i
 | |
|       end
 | |
|     rescue ArgumentError
 | |
|     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 +kind+ 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, options = {})
 | |
|       # Before touching this code, please double check RFC 2616 14.24 and 14.26.
 | |
|       options      = {:kind => options} unless Hash === options
 | |
|       kind         = options[:kind] || :strong
 | |
|       new_resource = options.fetch(:new_resource) { request.post? }
 | |
| 
 | |
|       unless [:strong, :weak].include?(kind)
 | |
|         raise ArgumentError, ":strong or :weak expected"
 | |
|       end
 | |
| 
 | |
|       value = '"%s"' % value
 | |
|       value = 'W/' + value if kind == :weak
 | |
|       response['ETag'] = value
 | |
| 
 | |
|       if success? or status == 304
 | |
|         if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
 | |
|           halt(request.safe? ? 304 : 412)
 | |
|         end
 | |
| 
 | |
|         if env['HTTP_IF_MATCH']
 | |
|           halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource
 | |
|         end
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Sugar for redirect (example:  redirect back)
 | |
|     def back
 | |
|       request.referer
 | |
|     end
 | |
| 
 | |
|     # whether or not the status is set to 1xx
 | |
|     def informational?
 | |
|       status.between? 100, 199
 | |
|     end
 | |
| 
 | |
|     # whether or not the status is set to 2xx
 | |
|     def success?
 | |
|       status.between? 200, 299
 | |
|     end
 | |
| 
 | |
|     # whether or not the status is set to 3xx
 | |
|     def redirect?
 | |
|       status.between? 300, 399
 | |
|     end
 | |
| 
 | |
|     # whether or not the status is set to 4xx
 | |
|     def client_error?
 | |
|       status.between? 400, 499
 | |
|     end
 | |
| 
 | |
|     # whether or not the status is set to 5xx
 | |
|     def server_error?
 | |
|       status.between? 500, 599
 | |
|     end
 | |
| 
 | |
|     # whether or not the status is set to 404
 | |
|     def not_found?
 | |
|       status == 404
 | |
|     end
 | |
| 
 | |
|     # Generates a Time object from the given value.
 | |
|     # Used by #expires and #last_modified.
 | |
|     def time_for(value)
 | |
|       if value.respond_to? :to_time
 | |
|         value.to_time
 | |
|       elsif value.is_a? Time
 | |
|         value
 | |
|       elsif value.respond_to? :new_offset
 | |
|         # DateTime#to_time does the same on 1.9
 | |
|         d = value.new_offset 0
 | |
|         t = Time.utc d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction
 | |
|         t.getlocal
 | |
|       elsif value.respond_to? :mday
 | |
|         # Date#to_time does the same on 1.9
 | |
|         Time.local(value.year, value.mon, value.mday)
 | |
|       elsif value.is_a? Numeric
 | |
|         Time.at value
 | |
|       else
 | |
|         Time.parse value.to_s
 | |
|       end
 | |
|     rescue ArgumentError => boom
 | |
|       raise boom
 | |
|     rescue Exception
 | |
|       raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
 | |
|     end
 | |
| 
 | |
|     private
 | |
| 
 | |
|     # Helper method checking if a ETag value list includes the current ETag.
 | |
|     def etag_matches?(list, new_resource = request.post?)
 | |
|       return !new_resource if list == '*'
 | |
|       list.to_s.split(/\s*,\s*/).include? response['ETag']
 | |
|     end
 | |
| 
 | |
|     def with_params(temp_params)
 | |
|       original, @params = @params, temp_params
 | |
|       yield
 | |
|     ensure
 | |
|       @params = original if original
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   private
 | |
| 
 | |
|   # Template rendering methods. Each method takes 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:
 | |
|   #   :content_type   The content type to use, same arguments as content_type.
 | |
|   #   :layout         If set to false, no layout is rendered, otherwise
 | |
|   #                   the specified layout is used (Ignored for `sass` and `less`)
 | |
|   #   :layout_engine  Engine to use for rendering the layout.
 | |
|   #   :locals         A hash with local variables that should be available
 | |
|   #                   in the template
 | |
|   #   :scope          If set, template is evaluate with the binding of the given
 | |
|   #                   object rather than the application instance.
 | |
|   #   :views          Views directory to use.
 | |
|   module Templates
 | |
|     module ContentTyped
 | |
|       attr_accessor :content_type
 | |
|     end
 | |
| 
 | |
|     def initialize
 | |
|       super
 | |
|       @default_layout = :layout
 | |
|     end
 | |
| 
 | |
|     def erb(template, options={}, locals={})
 | |
|       render :erb, template, options, locals
 | |
|     end
 | |
| 
 | |
|     def erubis(template, options={}, locals={})
 | |
|       warn "Sinatra::Templates#erubis is deprecated and will be removed, use #erb instead.\n" \
 | |
|         "If you have Erubis installed, it will be used automatically."
 | |
|       render :erubis, template, options, locals
 | |
|     end
 | |
| 
 | |
|     def haml(template, options={}, locals={})
 | |
|       render :haml, template, options, locals
 | |
|     end
 | |
| 
 | |
|     def sass(template, options={}, locals={})
 | |
|       options.merge! :layout => false, :default_content_type => :css
 | |
|       render :sass, template, options, locals
 | |
|     end
 | |
| 
 | |
|     def scss(template, options={}, locals={})
 | |
|       options.merge! :layout => false, :default_content_type => :css
 | |
|       render :scss, template, options, locals
 | |
|     end
 | |
| 
 | |
|     def less(template, options={}, locals={})
 | |
|       options.merge! :layout => false, :default_content_type => :css
 | |
|       render :less, template, options, locals
 | |
|     end
 | |
| 
 | |
|     def builder(template=nil, options={}, locals={}, &block)
 | |
|       options[:default_content_type] = :xml
 | |
|       render_ruby(:builder, template, options, locals, &block)
 | |
|     end
 | |
| 
 | |
|     def liquid(template, options={}, locals={})
 | |
|       render :liquid, template, options, locals
 | |
|     end
 | |
| 
 | |
|     def markdown(template, options={}, locals={})
 | |
|       render :markdown, template, options, locals
 | |
|     end
 | |
| 
 | |
|     def textile(template, options={}, locals={})
 | |
|       render :textile, template, options, locals
 | |
|     end
 | |
| 
 | |
|     def rdoc(template, options={}, locals={})
 | |
|       render :rdoc, template, options, locals
 | |
|     end
 | |
| 
 | |
|     def radius(template, options={}, locals={})
 | |
|       render :radius, template, options, locals
 | |
|     end
 | |
| 
 | |
|     def markaby(template=nil, options={}, locals={}, &block)
 | |
|       render_ruby(:mab, template, options, locals, &block)
 | |
|     end
 | |
| 
 | |
|     def coffee(template, options={}, locals={})
 | |
|       options.merge! :layout => false, :default_content_type => :js
 | |
|       render :coffee, template, options, locals
 | |
|     end
 | |
| 
 | |
|     def nokogiri(template=nil, options={}, locals={}, &block)
 | |
|       options[:default_content_type] = :xml
 | |
|       render_ruby(:nokogiri, template, options, locals, &block)
 | |
|     end
 | |
| 
 | |
|     def slim(template, options={}, locals={})
 | |
|       render :slim, template, options, locals
 | |
|     end
 | |
| 
 | |
|     def creole(template, options={}, locals={})
 | |
|       render :creole, template, options, locals
 | |
|     end
 | |
| 
 | |
|     def yajl(template, options={}, locals={})
 | |
|       options[:default_content_type] = :json
 | |
|       render :yajl, template, options, locals
 | |
|     end
 | |
| 
 | |
|     # Calls the given block for every possible template file in views,
 | |
|     # named name.ext, where ext is registered on engine.
 | |
|     def find_template(views, name, engine)
 | |
|       yield ::File.join(views, "#{name}.#{@preferred_extension}")
 | |
|       Tilt.mappings.each do |ext, engines|
 | |
|         next unless ext != @preferred_extension and engines.include? engine
 | |
|         yield ::File.join(views, "#{name}.#{ext}")
 | |
|       end
 | |
|     end
 | |
| 
 | |
|   private
 | |
|     # logic shared between builder and nokogiri
 | |
|     def render_ruby(engine, template, options={}, locals={}, &block)
 | |
|       options, template = template, nil if template.is_a?(Hash)
 | |
|       template = Proc.new { block } if template.nil?
 | |
|       render engine, template, options, locals
 | |
|     end
 | |
| 
 | |
|     def render(engine, data, options={}, locals={}, &block)
 | |
|       # merge app-level options
 | |
|       options = settings.send(engine).merge(options) if settings.respond_to?(engine)
 | |
|       options[:outvar]           ||= '@_out_buf'
 | |
|       options[:default_encoding] ||= settings.default_encoding
 | |
| 
 | |
|       # extract generic options
 | |
|       locals          = options.delete(:locals) || locals         || {}
 | |
|       views           = options.delete(:views)  || settings.views || "./views"
 | |
|       layout          = options.delete(:layout)
 | |
|       eat_errors      = layout.nil?
 | |
|       layout          = @default_layout if layout.nil? or layout == true
 | |
|       content_type    = options.delete(:content_type)  || options.delete(:default_content_type)
 | |
|       layout_engine   = options.delete(:layout_engine) || engine
 | |
|       scope           = options.delete(:scope)         || self
 | |
| 
 | |
|       # compile and render template
 | |
|       begin
 | |
|         layout_was      = @default_layout
 | |
|         @default_layout = false
 | |
|         template        = compile_template(engine, data, options, views)
 | |
|         output          = template.render(scope, locals, &block)
 | |
|       ensure
 | |
|         @default_layout = layout_was
 | |
|       end
 | |
| 
 | |
|       # render layout
 | |
|       if layout
 | |
|         options = options.merge(:views => views, :layout => false, :eat_errors => eat_errors, :scope => scope)
 | |
|         catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } }
 | |
|       end
 | |
| 
 | |
|       output.extend(ContentTyped).content_type = content_type if content_type
 | |
|       output
 | |
|     end
 | |
| 
 | |
|     def compile_template(engine, data, options, views)
 | |
|       eat_errors = options.delete :eat_errors
 | |
|       template_cache.fetch engine, data, options do
 | |
|         template = Tilt[engine]
 | |
|         raise "Template engine not found: #{engine}" if template.nil?
 | |
| 
 | |
|         case data
 | |
|         when Symbol
 | |
|           body, path, line = settings.templates[data]
 | |
|           if body
 | |
|             body = body.call if body.respond_to?(:call)
 | |
|             template.new(path, line.to_i, options) { body }
 | |
|           else
 | |
|             found = false
 | |
|             @preferred_extension = engine.to_s
 | |
|             find_template(views, data, template) do |file|
 | |
|               path ||= file # keep the initial path rather than the last one
 | |
|               if found = File.exists?(file)
 | |
|                 path = file
 | |
|                 break
 | |
|               end
 | |
|             end
 | |
|             throw :layout_missing if eat_errors and not found
 | |
|             template.new(path, 1, options)
 | |
|           end
 | |
|         when Proc, String
 | |
|           body = data.is_a?(String) ? Proc.new { data } : data
 | |
|           path, line = settings.caller_locations.first
 | |
|           template.new(path, line.to_i, options, &body)
 | |
|         else
 | |
|           raise ArgumentError, "Sorry, don't know how to render #{data.inspect}."
 | |
|         end
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   # Base class for all Sinatra applications and middleware.
 | |
|   class Base
 | |
|     include Rack::Utils
 | |
|     include Helpers
 | |
|     include Templates
 | |
| 
 | |
|     attr_accessor :app
 | |
|     attr_reader   :template_cache
 | |
| 
 | |
|     def initialize(app=nil)
 | |
|       super()
 | |
|       @app = app
 | |
|       @template_cache = Tilt::Cache.new
 | |
|       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) # :nodoc:
 | |
|       @env      = env
 | |
|       @request  = Request.new(env)
 | |
|       @response = Response.new
 | |
|       @params   = indifferent_params(@request.params)
 | |
|       template_cache.clear if settings.reload_templates
 | |
|       force_encoding(@params)
 | |
| 
 | |
|       @response['Content-Type'] = nil
 | |
|       invoke { dispatch! }
 | |
|       invoke { error_block!(response.status) }
 | |
| 
 | |
|       unless @response['Content-Type']
 | |
|         if Array === body and body[0].respond_to? :content_type
 | |
|           content_type body[0].content_type
 | |
|         else
 | |
|           content_type :html
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       @response.finish
 | |
|     end
 | |
| 
 | |
|     # Access settings defined with Base.set.
 | |
|     def self.settings
 | |
|       self
 | |
|     end
 | |
| 
 | |
|     # Access settings defined with Base.set.
 | |
|     def settings
 | |
|       self.class.settings
 | |
|     end
 | |
| 
 | |
|     def options
 | |
|       warn "Sinatra::Base#options is deprecated and will be removed, " \
 | |
|         "use #settings instead."
 | |
|       settings
 | |
|     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(&block)
 | |
|       throw :pass, block
 | |
|     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 env
 | |
|       @response.status = status
 | |
|       @response.body = body
 | |
|       @response.headers.merge! headers
 | |
|       nil
 | |
|     end
 | |
| 
 | |
|   private
 | |
|     # Run filters defined on the class and all superclasses.
 | |
|     def filter!(type, base = settings)
 | |
|       filter! type, base.superclass if base.superclass.respond_to?(:filters)
 | |
|       base.filters[type].each { |args| process_route(*args) }
 | |
|     end
 | |
| 
 | |
|     # Run routes defined on the class and all superclasses.
 | |
|     def route!(base = settings, pass_block=nil)
 | |
|       if routes = base.routes[@request.request_method]
 | |
|         routes.each do |pattern, keys, conditions, block|
 | |
|           pass_block = process_route(pattern, keys, conditions) do |*args|
 | |
|             route_eval { block[*args] }
 | |
|           end
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       # Run routes defined in superclass.
 | |
|       if base.superclass.respond_to?(:routes)
 | |
|         return route!(base.superclass, pass_block)
 | |
|       end
 | |
| 
 | |
|       route_eval(&pass_block) if pass_block
 | |
|       route_missing
 | |
|     end
 | |
| 
 | |
|     # Run a route block and throw :halt with the result.
 | |
|     def route_eval
 | |
|       throw :halt, yield
 | |
|     end
 | |
| 
 | |
|     # If the current request matches pattern and conditions, fill params
 | |
|     # with keys and call the given block.
 | |
|     # Revert params afterwards.
 | |
|     #
 | |
|     # Returns pass block.
 | |
|     def process_route(pattern, keys, conditions, block = nil, values = [])
 | |
|       route = @request.path_info
 | |
|       route = '/' if route.empty? and not settings.empty_path_info?
 | |
|       return unless match = pattern.match(route)
 | |
|       values += match.captures.to_a.map { |v| force_encoding URI.decode_www_form_component(v) if v }
 | |
| 
 | |
|       if values.any?
 | |
|         original, @params = params, params.merge('splat' => [], 'captures' => values)
 | |
|         keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v }
 | |
|       end
 | |
| 
 | |
|       catch(:pass) do
 | |
|         conditions.each { |c| throw :pass if c.bind(self).call == false }
 | |
|         block ? block[self, values] : yield(self, values)
 | |
|       end
 | |
|     ensure
 | |
|       @params = original if original
 | |
|     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
 | |
| 
 | |
|     # Attempt to serve static files from public directory. Throws :halt when
 | |
|     # a matching file is found, returns nil otherwise.
 | |
|     def static!
 | |
|       return if (public_dir = settings.public_folder).nil?
 | |
|       public_dir = File.expand_path(public_dir)
 | |
| 
 | |
|       path = File.expand_path(public_dir + unescape(request.path_info))
 | |
|       return unless path.start_with?(public_dir) and File.file?(path)
 | |
| 
 | |
|       env['sinatra.static_file'] = path
 | |
|       cache_control(*settings.static_cache_control) if settings.static_cache_control?
 | |
|       send_file path, :disposition => nil
 | |
|     end
 | |
| 
 | |
|     # Enable string or symbol key access to the nested params hash.
 | |
|     def indifferent_params(object)
 | |
|       case object
 | |
|       when Hash
 | |
|         new_hash = indifferent_hash
 | |
|         object.each { |key, value| new_hash[key] = indifferent_params(value) }
 | |
|         new_hash
 | |
|       when Array
 | |
|         object.map { |item| indifferent_params(item) }
 | |
|       else
 | |
|         object
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Creates a Hash with indifferent access.
 | |
|     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
 | |
|       res = catch(:halt) { yield }
 | |
|       res = [res] if Fixnum === res or String === res
 | |
|       if Array === res and Fixnum === res.first
 | |
|         status(res.shift)
 | |
|         body(res.pop)
 | |
|         headers(*res)
 | |
|       elsif res.respond_to? :each
 | |
|         body res
 | |
|       end
 | |
|       nil # avoid double setting the same response tuple twice
 | |
|     end
 | |
| 
 | |
|     # Dispatch a request with error handling.
 | |
|     def dispatch!
 | |
|       invoke do
 | |
|         static! if settings.static? && (request.get? || request.head?)
 | |
|         filter! :before
 | |
|         route!
 | |
|       end
 | |
|     rescue ::Exception => boom
 | |
|       invoke { handle_exception!(boom) }
 | |
|     ensure
 | |
|       filter! :after unless env['sinatra.static_file']
 | |
|     end
 | |
| 
 | |
|     # Error handling during requests.
 | |
|     def handle_exception!(boom)
 | |
|       @env['sinatra.error'] = boom
 | |
| 
 | |
|       if boom.respond_to? :http_status
 | |
|         status(boom.http_status)
 | |
|       elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599
 | |
|         status(boom.code)
 | |
|       else
 | |
|         status(500)
 | |
|       end
 | |
| 
 | |
|       status(500) unless status.between? 400, 599
 | |
| 
 | |
|       if server_error?
 | |
|         dump_errors! boom if settings.dump_errors?
 | |
|         raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
 | |
|       end
 | |
| 
 | |
|       if not_found?
 | |
|         headers['X-Cascade'] = 'pass'
 | |
|         body '<h1>Not Found</h1>'
 | |
|       end
 | |
| 
 | |
|       res = error_block!(boom.class, boom) || error_block!(status, boom)
 | |
|       return res if res or not server_error?
 | |
|       raise boom if settings.raise_errors? or settings.show_exceptions?
 | |
|       error_block! Exception, boom
 | |
|     end
 | |
| 
 | |
|     # Find an custom error block for the key(s) specified.
 | |
|     def error_block!(key, *block_params)
 | |
|       base = settings
 | |
|       while base.respond_to?(:errors)
 | |
|         next base = base.superclass unless args_array = base.errors[key]
 | |
|         args_array.reverse_each do |args|
 | |
|           first = args == args_array.first
 | |
|           args += [block_params]
 | |
|           resp = process_route(*args)
 | |
|           return resp unless resp.nil? && !first
 | |
|         end
 | |
|       end
 | |
|       return false unless key.respond_to? :superclass and key.superclass < Exception
 | |
|       error_block!(key.superclass, *block_params)
 | |
|     end
 | |
| 
 | |
|     def dump_errors!(boom)
 | |
|       msg = ["#{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t")
 | |
|       @env['rack.errors'].puts(msg)
 | |
|     end
 | |
| 
 | |
|     class << self
 | |
|       attr_reader :routes, :filters, :templates, :errors
 | |
| 
 | |
|       # Removes all routes, filters, middleware and extension hooks from the
 | |
|       # current class (not routes/filters/... defined by its superclass).
 | |
|       def reset!
 | |
|         @conditions     = []
 | |
|         @routes         = {}
 | |
|         @filters        = {:before => [], :after => []}
 | |
|         @errors         = {}
 | |
|         @middleware     = []
 | |
|         @prototype      = nil
 | |
|         @extensions     = []
 | |
| 
 | |
|         if superclass.respond_to?(:templates)
 | |
|           @templates = Hash.new { |hash,key| superclass.templates[key] }
 | |
|         else
 | |
|           @templates = {}
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       # Extension modules registered on this class and all superclasses.
 | |
|       def extensions
 | |
|         if superclass.respond_to?(:extensions)
 | |
|           (@extensions + superclass.extensions).uniq
 | |
|         else
 | |
|           @extensions
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       # Middleware used in this class and all superclasses.
 | |
|       def middleware
 | |
|         if superclass.respond_to?(:middleware)
 | |
|           superclass.middleware + @middleware
 | |
|         else
 | |
|           @middleware
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       # 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 = (not_set = true), ignore_setter = false, &block)
 | |
|         raise ArgumentError if block and !not_set
 | |
|         value, not_set = block, false if block
 | |
| 
 | |
|         if not_set
 | |
|           raise ArgumentError unless option.respond_to?(:each)
 | |
|           option.each { |k,v| set(k, v) }
 | |
|           return self
 | |
|         end
 | |
| 
 | |
|         if respond_to?("#{option}=") and not ignore_setter
 | |
|           return __send__("#{option}=", value)
 | |
|         end
 | |
| 
 | |
|         setter = proc { |val| set option, val, true }
 | |
|         getter = proc { value }
 | |
| 
 | |
|         case value
 | |
|         when Proc
 | |
|           getter = value
 | |
|         when Symbol, Fixnum, FalseClass, TrueClass, NilClass
 | |
|           getter = value.inspect
 | |
|         when Hash
 | |
|           setter = proc do |val|
 | |
|             val = value.merge val if Hash === val
 | |
|             set option, val, true
 | |
|           end
 | |
|         end
 | |
| 
 | |
|         define_singleton_method("#{option}=", setter) if setter
 | |
|         define_singleton_method(option, getter) if getter
 | |
|         define_singleton_method("#{option}?", "!!#{option}") unless method_defined? "#{option}?"
 | |
|         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, &block)
 | |
|         args  = compile! "ERROR", //, block
 | |
|         codes = codes.map { |c| Array(c) }.flatten
 | |
|         codes << Exception if codes.empty?
 | |
|         codes.each { |c| (@errors[c] ||= []) << args }
 | |
|       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] = [block, filename, line.to_i]
 | |
|       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 inline_templates=(file=nil)
 | |
|         file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file
 | |
| 
 | |
|         begin
 | |
|           io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file)
 | |
|           app, data = io.gsub("\r\n", "\n").split(/^__END__$/, 2)
 | |
|         rescue Errno::ENOENT
 | |
|           app, data = nil
 | |
|         end
 | |
| 
 | |
|         if data
 | |
|           if app and app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m
 | |
|             encoding = $2
 | |
|           else
 | |
|             encoding = settings.default_encoding
 | |
|           end
 | |
|           lines = app.count("\n") + 1
 | |
|           template = nil
 | |
|           force_encoding data, encoding
 | |
|           data.each_line do |line|
 | |
|             lines += 1
 | |
|             if line =~ /^@@\s*(.*\S)\s*$/
 | |
|               template = force_encoding('', encoding)
 | |
|               templates[$1.to_sym] = [template, file, lines]
 | |
|             elsif template
 | |
|               template << line
 | |
|             end
 | |
|           end
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       # Lookup or register a mime type in Rack's mime registry.
 | |
|       def mime_type(type, value=nil)
 | |
|         return type if type.nil? || type.to_s.include?('/')
 | |
|         type = ".#{type}" unless type.to_s[0] == ?.
 | |
|         return Rack::Mime.mime_type(type, nil) unless value
 | |
|         Rack::Mime::MIME_TYPES[type] = value
 | |
|       end
 | |
| 
 | |
|       # provides all mime types matching type, including deprecated types:
 | |
|       #   mime_types :html # => ['text/html']
 | |
|       #   mime_types :js   # => ['application/javascript', 'text/javascript']
 | |
|       def mime_types(type)
 | |
|         type = mime_type type
 | |
|         type =~ /^application\/(xml|javascript)$/ ? [type, "text/#$1"] : [type]
 | |
|       end
 | |
| 
 | |
|       # Define a before filter; runs before all requests within the same
 | |
|       # context as route handlers and may access/modify the request and
 | |
|       # response.
 | |
|       def before(path = nil, options = {}, &block)
 | |
|         add_filter(:before, path, options, &block)
 | |
|       end
 | |
| 
 | |
|       # Define an after filter; runs after all requests within the same
 | |
|       # context as route handlers and may access/modify the request and
 | |
|       # response.
 | |
|       def after(path = nil, options = {}, &block)
 | |
|         add_filter(:after, path, options, &block)
 | |
|       end
 | |
| 
 | |
|       # add a filter
 | |
|       def add_filter(type, path = nil, options = {}, &block)
 | |
|         path, options = //, path if path.respond_to?(:each_pair)
 | |
|         filters[type] << compile!(type, path || //, block, options)
 | |
|       end
 | |
| 
 | |
|       # Add a route condition. The route is considered non-matching when the
 | |
|       # block returns false.
 | |
|       def condition(name = "#{caller.first[/`.*'/]} condition", &block)
 | |
|         @conditions << generate_method(name, &block)
 | |
|       end
 | |
| 
 | |
|       def public=(value)
 | |
|         warn ":public is no longer used to avoid overloading Module#public, use :public_dir instead"
 | |
|         set(:public_folder, value)
 | |
|       end
 | |
| 
 | |
|       def public_dir=(value)
 | |
|         self.public_folder = value
 | |
|       end
 | |
| 
 | |
|       def public_dir
 | |
|         public_folder
 | |
|       end
 | |
| 
 | |
|     private
 | |
|       # Dynamically defines a method on settings.
 | |
|       def define_singleton_method(name, content = Proc.new)
 | |
|         # replace with call to singleton_class once we're 1.9 only
 | |
|         (class << self; self; end).class_eval do
 | |
|           undef_method(name) if method_defined? name
 | |
|           String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content)
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       # Condition for matching host name. Parameter might be String or Regexp.
 | |
|       def host_name(pattern)
 | |
|         condition { pattern === request.host }
 | |
|       end
 | |
| 
 | |
|       # Condition for matching user agent. Parameter should be Regexp.
 | |
|       # Will set params[:agent].
 | |
|       def user_agent(pattern)
 | |
|         condition do
 | |
|           if request.user_agent.to_s =~ pattern
 | |
|             @params[:agent] = $~[1..-1]
 | |
|             true
 | |
|           else
 | |
|             false
 | |
|           end
 | |
|         end
 | |
|       end
 | |
|       alias_method :agent, :user_agent
 | |
| 
 | |
|       # Condition for matching mimetypes. Accepts file extensions.
 | |
|       def provides(*types)
 | |
|         types.map! { |t| mime_types(t) }
 | |
|         types.flatten!
 | |
|         condition do
 | |
|           if type = response['Content-Type']
 | |
|             types.include? type or types.include? type[/^[^;]+/]
 | |
|           elsif type = request.preferred_type(types)
 | |
|             content_type(type)
 | |
|             true
 | |
|           else
 | |
|             false
 | |
|           end
 | |
|         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
 | |
|       def options(path, opts={}, &bk) route 'OPTIONS', path, opts, &bk end
 | |
|       def patch(path, opts={}, &bk)   route 'PATCH',   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)
 | |
|         enable :empty_path_info if path == "" and empty_path_info.nil?
 | |
|         signature = compile!(verb, path, block, options)
 | |
|         (@routes[verb] ||= []) << signature
 | |
|         invoke_hook(:route_added, verb, path, block)
 | |
|         signature
 | |
|       end
 | |
| 
 | |
|       def invoke_hook(name, *args)
 | |
|         extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
 | |
|       end
 | |
| 
 | |
|       def generate_method(method_name, &block)
 | |
|         define_method(method_name, &block)
 | |
|         method = instance_method method_name
 | |
|         remove_method method_name
 | |
|         method
 | |
|       end
 | |
| 
 | |
|       def compile!(verb, path, block, options = {})
 | |
|         options.each_pair { |option, args| send(option, *args) }
 | |
|         method_name             = "#{verb} #{path}"
 | |
|         unbound_method          = generate_method(method_name, &block)
 | |
|         pattern, keys           = compile path
 | |
|         conditions, @conditions = @conditions, []
 | |
| 
 | |
|         [ pattern, keys, conditions, block.arity != 0 ?
 | |
|             proc { |a,p| unbound_method.bind(a).call(*p) } :
 | |
|             proc { |a,p| unbound_method.bind(a).call } ]
 | |
|       end
 | |
| 
 | |
|       def compile(path)
 | |
|         keys = []
 | |
|         if path.respond_to? :to_str
 | |
|           pattern = path.to_str.gsub(/[^\?\%\\\/\:\*\w]/) { |c| encoded(c) }
 | |
|           pattern.gsub!(/((:\w+)|\*)/) do |match|
 | |
|             if match == "*"
 | |
|               keys << 'splat'
 | |
|               "(.*?)"
 | |
|             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?(:names) && path.respond_to?(:match)
 | |
|           [path, path.names]
 | |
|         elsif path.respond_to? :match
 | |
|           [path, keys]
 | |
|         else
 | |
|           raise TypeError, path
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       URI = ::URI.const_defined?(:Parser) ? ::URI::Parser.new : ::URI
 | |
| 
 | |
|       def encoded(char)
 | |
|         enc = URI.escape(char)
 | |
|         enc = "(?:#{Regexp.escape enc}|#{URI.escape char, /./})" if enc == char
 | |
|         enc = "(?:#{enc}|#{encoded('+')})" if char == " "
 | |
|         enc
 | |
|       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
 | |
| 
 | |
|       # Register an extension. Alternatively take a block from which an
 | |
|       # extension will be created and registered on the fly.
 | |
|       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
 | |
| 
 | |
|       def quit!(server, handler_name)
 | |
|         # Use Thin's hard #stop! if available, otherwise just #stop.
 | |
|         server.respond_to?(:stop!) ? server.stop! : server.stop
 | |
|         $stderr.puts "\n== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i
 | |
|       end
 | |
| 
 | |
|       # Run the Sinatra app as a self-hosted server using
 | |
|       # Thin, Puma, Mongrel, or WEBrick (in that order). If given a block, will call
 | |
|       # with the constructed handler once we have taken the stage.
 | |
|       def run!(options={})
 | |
|         set options
 | |
|         handler         = detect_rack_handler
 | |
|         handler_name    = handler.name.gsub(/.*::/, '')
 | |
|         server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
 | |
|         handler.run self, server_settings.merge(:Port => port, :Host => bind) do |server|
 | |
|           unless handler_name =~ /cgi/i
 | |
|             $stderr.puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
 | |
|             "on #{port} for #{environment} with backup from #{handler_name}"
 | |
|           end
 | |
|           [:INT, :TERM].each { |sig| trap(sig) { quit!(server, handler_name) } }
 | |
|           server.threaded = settings.threaded if server.respond_to? :threaded=
 | |
|           set :running, true
 | |
|           yield server if block_given?
 | |
|         end
 | |
|       rescue Errno::EADDRINUSE
 | |
|         $stderr.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 without middleware in front of it.
 | |
|       alias new! new unless method_defined? :new!
 | |
| 
 | |
|       # 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)
 | |
|         instance = new!(*args, &bk)
 | |
|         Wrapper.new(build(instance).to_app, instance)
 | |
|       end
 | |
| 
 | |
|       # Creates a Rack::Builder instance with all the middleware set up and
 | |
|       # the given +app+ as end point.
 | |
|       def build(app)
 | |
|         builder = Rack::Builder.new
 | |
|         setup_default_middleware builder
 | |
|         setup_middleware builder
 | |
|         builder.run app
 | |
|         builder
 | |
|       end
 | |
| 
 | |
|       def call(env)
 | |
|         synchronize { prototype.call(env) }
 | |
|       end
 | |
| 
 | |
|     private
 | |
|       def setup_default_middleware(builder)
 | |
|         builder.use ExtendedRack
 | |
|         builder.use ShowExceptions       if show_exceptions?
 | |
|         builder.use Rack::MethodOverride if method_override?
 | |
|         builder.use Rack::Head
 | |
|         setup_logging    builder
 | |
|         setup_sessions   builder
 | |
|         setup_protection builder
 | |
|       end
 | |
| 
 | |
|       def setup_middleware(builder)
 | |
|         middleware.each { |c,a,b| builder.use(c, *a, &b) }
 | |
|       end
 | |
| 
 | |
|       def setup_logging(builder)
 | |
|         if logging?
 | |
|           setup_common_logger(builder)
 | |
|           setup_custom_logger(builder)
 | |
|         elsif logging == false
 | |
|           setup_null_logger(builder)
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       def setup_null_logger(builder)
 | |
|         builder.use Rack::NullLogger
 | |
|       end
 | |
| 
 | |
|       def setup_common_logger(builder)
 | |
|         builder.use Sinatra::CommonLogger
 | |
|       end
 | |
| 
 | |
|       def setup_custom_logger(builder)
 | |
|         if logging.respond_to? :to_int
 | |
|           builder.use Rack::Logger, logging
 | |
|         else
 | |
|           builder.use Rack::Logger
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       def setup_protection(builder)
 | |
|         return unless protection?
 | |
|         options = Hash === protection ? protection.dup : {}
 | |
|         options[:except] = Array options[:except]
 | |
|         options[:except] += [:session_hijacking, :remote_token] unless sessions?
 | |
|         options[:reaction] ||= :drop_session
 | |
|         builder.use Rack::Protection, options
 | |
|       end
 | |
| 
 | |
|       def setup_sessions(builder)
 | |
|         return unless sessions?
 | |
|         options = {}
 | |
|         options[:secret] = session_secret if session_secret?
 | |
|         options.merge! sessions.to_hash if sessions.respond_to? :to_hash
 | |
|         builder.use Rack::Session::Cookie, options
 | |
|       end
 | |
| 
 | |
|       def detect_rack_handler
 | |
|         servers = Array(server)
 | |
|         servers.each do |server_name|
 | |
|           begin
 | |
|             return Rack::Handler.get(server_name.to_s)
 | |
|           rescue LoadError, NameError
 | |
|           end
 | |
|         end
 | |
|         fail "Server handler (#{servers.join(',')}) not found."
 | |
|       end
 | |
| 
 | |
|       def inherited(subclass)
 | |
|         subclass.reset!
 | |
|         subclass.set :app_file, caller_files.first unless subclass.app_file?
 | |
|         super
 | |
|       end
 | |
| 
 | |
|       @@mutex = Mutex.new
 | |
|       def synchronize(&block)
 | |
|         if lock?
 | |
|           @@mutex.synchronize(&block)
 | |
|         else
 | |
|           yield
 | |
|         end
 | |
|       end
 | |
| 
 | |
|     public
 | |
|       CALLERS_TO_IGNORE = [ # :nodoc:
 | |
|         /\/sinatra(\/(base|main|showexceptions))?\.rb$/, # all sinatra code
 | |
|         /lib\/tilt.*\.rb$/,                              # all tilt code
 | |
|         /^\(.*\)$/,                                      # generated code
 | |
|         /rubygems\/custom_require\.rb$/,                 # rubygems require hacks
 | |
|         /active_support/,                                # active_support require hacks
 | |
|         /bundler(\/runtime)?\.rb/,                       # bundler require hacks
 | |
|         /<internal:/,                                    # internal in ruby >= 1.9.2
 | |
|         /src\/kernel\/bootstrap\/[A-Z]/                  # maglev kernel files
 | |
|       ]
 | |
| 
 | |
|       # contrary to what the comment said previously, rubinius never supported this
 | |
|       if defined?(RUBY_IGNORE_CALLERS)
 | |
|         warn "RUBY_IGNORE_CALLERS is deprecated and will no longer be supported by Sinatra 2.0"
 | |
|         CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS)
 | |
|       end
 | |
| 
 | |
|       # Like Kernel#caller but excluding certain magic entries and without
 | |
|       # line / method information; the resulting array contains filenames only.
 | |
|       def caller_files
 | |
|         cleaned_caller(1).flatten
 | |
|       end
 | |
| 
 | |
|       # Like caller_files, but containing Arrays rather than strings with the
 | |
|       # first element being the file, and the second being the line.
 | |
|       def caller_locations
 | |
|         cleaned_caller 2
 | |
|       end
 | |
| 
 | |
|     private
 | |
|       # used for deprecation warnings
 | |
|       def warn(message)
 | |
|         super message + "\n\tfrom #{cleaned_caller.first.join(':')}"
 | |
|       end
 | |
| 
 | |
|       # Like Kernel#caller but excluding certain magic entries
 | |
|       def cleaned_caller(keep = 3)
 | |
|         caller(1).
 | |
|           map    { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }.
 | |
|           reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Fixes encoding issues by
 | |
|     # * defaulting to UTF-8
 | |
|     # * casting params to Encoding.default_external
 | |
|     #
 | |
|     # The latter might not be necessary if Rack handles it one day.
 | |
|     # Keep an eye on Rack's LH #100.
 | |
|     def force_encoding(*args) settings.force_encoding(*args) end
 | |
|     if defined? Encoding
 | |
|       def self.force_encoding(data, encoding = default_encoding)
 | |
|         return if data == settings || data.is_a?(Tempfile)
 | |
|         if data.respond_to? :force_encoding
 | |
|           data.force_encoding(encoding).encode!
 | |
|         elsif data.respond_to? :each_value
 | |
|           data.each_value { |v| force_encoding(v, encoding) }
 | |
|         elsif data.respond_to? :each
 | |
|           data.each { |v| force_encoding(v, encoding) }
 | |
|         end
 | |
|         data
 | |
|       end
 | |
|     else
 | |
|       def self.force_encoding(data, *) data end
 | |
|     end
 | |
| 
 | |
|     reset!
 | |
| 
 | |
|     set :environment, (ENV['RACK_ENV'] || :development).to_sym
 | |
|     set :raise_errors, Proc.new { test? }
 | |
|     set :dump_errors, Proc.new { !test? }
 | |
|     set :show_exceptions, Proc.new { development? }
 | |
|     set :sessions, false
 | |
|     set :logging, false
 | |
|     set :protection, true
 | |
|     set :method_override, false
 | |
|     set :use_code, false
 | |
|     set :default_encoding, "utf-8"
 | |
|     set :add_charset, %w[javascript xml xhtml+xml json].map { |t| "application/#{t}" }
 | |
|     settings.add_charset << /^text\//
 | |
| 
 | |
|     # explicitly generating a session secret eagerly to play nice with preforking
 | |
|     begin
 | |
|       require 'securerandom'
 | |
|       set :session_secret, SecureRandom.hex(64)
 | |
|     rescue LoadError, NotImplementedError
 | |
|       # SecureRandom raises a NotImplementedError if no random device is available
 | |
|       set :session_secret, "%064x" % Kernel.rand(2**256-1)
 | |
|     end
 | |
| 
 | |
|     class << self
 | |
|       alias_method :methodoverride?, :method_override?
 | |
|       alias_method :methodoverride=, :method_override=
 | |
|     end
 | |
| 
 | |
|     set :run, false                       # start server via at-exit hook?
 | |
|     set :running, false                   # is the built-in server running now?
 | |
|     set :server, %w[http webrick]
 | |
|     set :bind, '0.0.0.0'
 | |
|     set :port, 4567
 | |
| 
 | |
|     ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
 | |
| 
 | |
|     if ruby_engine == 'macruby'
 | |
|       server.unshift 'controll_tower'
 | |
|     else
 | |
|       server.unshift 'mongrel'  if ruby_engine.nil?
 | |
|       server.unshift 'puma'     if ruby_engine != 'rbx'
 | |
|       server.unshift 'thin'     if ruby_engine != 'jruby'
 | |
|       server.unshift 'puma'     if ruby_engine == 'rbx'
 | |
|       server.unshift 'trinidad' if ruby_engine =='jruby'
 | |
|     end
 | |
| 
 | |
|     set :absolute_redirects, true
 | |
|     set :prefixed_redirects, false
 | |
|     set :empty_path_info, nil
 | |
| 
 | |
|     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 :reload_templates, Proc.new { development? }
 | |
|     set :lock, false
 | |
|     set :threaded, true
 | |
| 
 | |
|     set :public_folder, Proc.new { root && File.join(root, 'public') }
 | |
|     set :static, Proc.new { public_folder && File.exist?(public_folder) }
 | |
|     set :static_cache_control, false
 | |
| 
 | |
|     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='#{uri "/__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
 | |
| 
 | |
|   # Execution context for classic style (top-level) applications. All
 | |
|   # DSL methods executed on main are delegated to this class.
 | |
|   #
 | |
|   # The Application class should not be subclassed, unless you want to
 | |
|   # inherit all settings, routes, handlers, and error pages from the
 | |
|   # top-level. Subclassing Sinatra::Base is highly recommended for
 | |
|   # modular applications.
 | |
|   class Application < Base
 | |
|     set :logging, Proc.new { ! test? }
 | |
|     set :method_override, true
 | |
|     set :run, Proc.new { ! test? }
 | |
|     set :session_secret, Proc.new { super() unless development? }
 | |
|     set :app_file, nil
 | |
| 
 | |
|     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
 | |
| 
 | |
|   # 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|
 | |
|         define_method(method_name) do |*args, &block|
 | |
|           return super(*args, &block) if respond_to? method_name
 | |
|           Delegator.target.send(method_name, *args, &block)
 | |
|         end
 | |
|         private method_name
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     delegate :get, :patch, :put, :post, :delete, :head, :options, :template, :layout,
 | |
|              :before, :after, :error, :not_found, :configure, :set, :mime_type,
 | |
|              :enable, :disable, :use, :development?, :test?, :production?,
 | |
|              :helpers, :settings
 | |
| 
 | |
|     class << self
 | |
|       attr_accessor :target
 | |
|     end
 | |
| 
 | |
|     self.target = Application
 | |
|   end
 | |
| 
 | |
|   class Wrapper
 | |
|     def initialize(stack, instance)
 | |
|       @stack, @instance = stack, instance
 | |
|     end
 | |
| 
 | |
|     def settings
 | |
|       @instance.settings
 | |
|     end
 | |
| 
 | |
|     def helpers
 | |
|       @instance
 | |
|     end
 | |
| 
 | |
|     def call(env)
 | |
|       @stack.call(env)
 | |
|     end
 | |
| 
 | |
|     def inspect
 | |
|       "#<#{@instance.class} app_file=#{settings.app_file.inspect}>"
 | |
|     end
 | |
|   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.class_eval(&block) if block_given?
 | |
|     base
 | |
|   end
 | |
| 
 | |
|   # Extend the top-level DSL with the modules provided.
 | |
|   def self.register(*extensions, &block)
 | |
|     Delegator.target.register(*extensions, &block)
 | |
|   end
 | |
| 
 | |
|   # Include the helper modules provided in Sinatra's request context.
 | |
|   def self.helpers(*extensions, &block)
 | |
|     Delegator.target.helpers(*extensions, &block)
 | |
|   end
 | |
| 
 | |
|   # Use the middleware for classic applications.
 | |
|   def self.use(*args, &block)
 | |
|     Delegator.target.use(*args, &block)
 | |
|   end
 | |
| end
 |