require 'sinatra/base'
require 'sinatra/accept_media_types'
# Accept header parsing was looked at but deemed
# too much of an irregularity to deal with. Problems with the header
# differences from IE, Firefox, Safari, and every other UA causes
# problems with the expected output. The general expected behavior
# would be serve html when no extension provided, but most UAs say
# they will accept application/xml with out a quality indicator, meaning
# you'd get the xml block served insead. Just plain retarded, use the
# extension and you'll never be suprised.
module Sinatra
module RespondTo
class UnhandledFormat < Sinatra::NotFound; end
class MissingTemplate < Sinatra::NotFound
def code; 500 end
end
TEXT_MIME_TYPES = [:txt, :html, :js, :json, :xml, :rss, :atom, :css, :asm, :c, :cc, :conf,
:csv, :cxx, :diff, :dtd, :f, :f77, :f90, :for, :gemspec, :h, :hh, :htm,
:log, :mathml, :mml, :p, :pas, :pl, :pm, :py, :rake, :rb, :rdf, :rtf, :ru,
:s, :sgm, :sgml, :sh, :svg, :svgz, :text, :wsdl, :xhtml, :xsl, :xslt, :yaml,
:yml, :ics, :png]
def self.registered(app)
app.helpers RespondTo::Helpers
app.set :default_charset, 'utf-8'
app.set :default_content, :html
app.set :assume_xhr_is_js, true
# We remove the trailing extension so routes
# don't have to be of the style
#
# get '/resouce.:format'
#
# They can instead be of the style
#
# get '/resource'
#
# and the format will automatically be available in format
app.before do
# Let through sinatra image urls in development
next if self.class.development? && request.path_info =~ %r{/__sinatra__/.*?.png}
unless options.static? && options.public? && (request.get? || request.head?) && static_file?(request.path_info)
rpi = request.path_info.sub(%r{\.([^\./]+)$}, '')
if (not $1) or ($1 and TEXT_MIME_TYPES.include?($1.to_sym))
request.path_info, ext = rpi, nil
if ! $1.nil?
ext = $1
elsif env['HTTP_ACCEPT'].nil? || env['HTTP_ACCEPT'].empty?
ext = options.default_content
end
if ext
@mime_types = [ Helpers::mime_type(ext) ]
format ext
end
end
end
end
app.configure :development do |dev|
dev.error UnhandledFormat do
content_type :html, :charset => 'utf-8'
(<<-HTML).gsub(/^ {10}/, '')
Sinatra doesn't know this ditty.
Try this:
#{request.request_method.downcase} '#{request.path_info}' do\n respond_to do |wants|\n wants.#{format} { "Hello World" }\n end\nend
HTML
end
dev.error MissingTemplate do
content_type :html, :charset => 'utf-8'
response.status = request.env['sinatra.error'].code
engine = request.env['sinatra.error'].message.split('.').last
engine = 'haml' unless ['haml', 'builder', 'erb'].include? engine
path = File.basename(request.path_info)
path = "root" if path.nil? || path.empty?
format = engine == 'builder' ? 'xml' : 'html'
layout = case engine
when 'haml' then "!!!\n%html\n %body= yield"
when 'erb' then "\n \n <%= yield %>\n \n"
when 'builder' then ::Sinatra::VERSION =~ /^1.0/ ? "xml << yield" : "builder do |xml|\n xml << yield\nend"
end
layout = "app.#{format}.#{engine}\n#{escape_html(layout)}
"
(<<-HTML).gsub(/^ {10}/, '')
Sinatra can't find #{request.env['sinatra.error'].message}
Try this:
#{layout}
#{path}.#{format}.#{engine}
Hello World!
application.rb
#{request.request_method.downcase} '#{request.path_info}' do\n respond_to do |wants|\n wants.#{engine == 'builder' ? 'xml' : 'html'} { #{engine} :#{path}#{",\n#{' '*32}layout => :app" if layout} }\n end\nend
HTML
end
end
app.class_eval do
private
def accept_list
@mime_types || Rack::AcceptMediaTypes.new(env['HTTP_ACCEPT'] || '')
end
# Changes in 1.0 Sinatra reuse render for layout so we store
# the original value to tell us if this is an automatic attempt
# to do a layout call. If it is, it might fail with Errno::ENOENT
# and we want to pass that back to sinatra since it isn't a MissingTemplate
# error
def render_with_format(*args, &block)
assumed_layout = args[1] == :layout
args[1] = "#{args[1]}.#{format}".to_sym if args[1].is_a?(::Symbol)
render_without_format *args, &block
rescue Errno::ENOENT => e
raise MissingTemplate, "#{args[1]}.#{args[0]}" unless assumed_layout
raise e
end
alias_method :render_without_format, :render
alias_method :render, :render_with_format
if ::Sinatra::VERSION =~ /^0\.9/
def lookup_layout_with_format(*args)
args[1] = "#{args[1]}.#{format}".to_sym if args[1].is_a?(::Symbol)
lookup_layout_without_format *args
end
alias_method :lookup_layout_without_format, :lookup_layout
alias_method :lookup_layout, :lookup_layout_with_format
end
end
end
module Helpers
# Patch the content_type function to remember the set type
# This helps cut down on time in the format helper so it
# doesn't have to do a reverse lookup on the header
def self.included(klass)
klass.class_eval do
def content_type_with_save(*args)
content_type_without_save *args
@_format = args.first.to_sym
response['Content-Type']
end
alias_method :content_type_without_save, :content_type
alias_method :content_type, :content_type_with_save
end if ::Sinatra::VERSION =~ /^1.0/
end
def self.mime_type(sym)
::Sinatra::Base.respond_to?(:mime_type) && ::Sinatra::Base.mime_type(sym) || ::Sinatra::Base.media_type(sym)
end
def format(val=nil)
unless val.nil?
mime_type = ::Sinatra::RespondTo::Helpers.mime_type(val)
fail "Unknown media type #{val}\nTry registering the extension with a mime type" if mime_type.nil?
@_format = val.to_sym
response['Content-Type'].sub!(/^[^;]+/, mime_type)
charset options.default_charset if Sinatra::RespondTo::TEXT_MIME_TYPES.include?(format) and format!=:png
end
@_format
end
# This is mostly just a helper so request.path_info isn't changed when
# serving files from the public directory
def static_file?(path)
public_dir = File.expand_path(options.public)
path = File.expand_path(File.join(public_dir, unescape(path)))
path[0, public_dir.length] == public_dir && File.file?(path)
end
def charset(val=nil)
fail "Content-Type must be set in order to specify a charset" if response['Content-Type'].nil?
if response['Content-Type'] =~ /charset=[^;]+/
response['Content-Type'].sub!(/charset=[^;]+/, (val == '' && '') || "charset=#{val}")
else
response['Content-Type'] += ";charset=#{val}"
end unless val.nil?
response['Content-Type'][/charset=([^;]+)/, 1]
end
def respond_to(&block)
wants = Format.new
yield wants
fmt, type, handler = match_accept_type(accept_list, wants)
raise UnhandledFormat if fmt.nil?
format fmt
handler.nil? ? nil : handler.call
end
def match_accept_type(mime_types, format)
selected = []
accepted_types = mime_types.map {|type| Regexp.escape(type).gsub(/\\\*/,'.*') }
# Fix for Chrome based browsers which returns XML when 'xhtml' is requested.
if env['HTTP_USER_AGENT'] =~ /Chrome/ and accepted_types.size>1
accepted_types[0], accepted_types[1] = accepted_types[1], accepted_types[0]
if accepted_types[0].eql?('application/xhtml\\+xml')
accepted_types[0] = 'text/html'
end
end
accepted_types.each do |at|
format.each do |fmt, ht, handler|
(selected = [fmt, ht, handler]) and break if ht.match(at)
end
break unless selected.empty?
end
selected
end
# NOTE Array instead of hash because order matters (wildcard type
# matches first handler)
class Format < Array #:nodoc:
def method_missing(format, *args, &handler)
mt = Sinatra::RespondTo::Helpers.mime_type(format)
if mt.nil?
Sinatra::Base.send(:fail, "Unknown media type for respond_to: #{format}\nTry registering the extension with a mime type")
end
self << [format.to_s, mt, handler]
end
end
end
end
end
Rack::Mime::MIME_TYPES.merge!({ ".gv" => "text/plain" })
Sinatra::Application.register Sinatra::RespondTo