4 require 'sinatra/mime_types'
7 old_verbose, $VERBOSE = $VERBOSE, nil
10 $VERBOSE = old_verbose
25 map { |k,v| "#{k}=#{URI.escape(v)}" }.join('&')
29 self.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
33 reject { |k,v| !keys.include?(k) }
39 Proc.new { |*args| args.shift.__send__(self, *args) }
45 self.inject({}) { |h, (k, v)| h[k] = v; h }
49 Proc.new { |*args| args.shift.__send__(self[0], args + self[1..-1]) }
61 find { |e| result = block[e] and break result }
68 EventContext = Struct.new(:request, :response, :route_params) do
70 @params ||= request.params.merge(route_params).symbolize_keys
73 def method_missing(name, *args)
74 if args.size == 1 && response.respond_to?("#{name}=")
75 response.send("#{name}=", args.first)
77 response.send(name, *args)
82 def setup_default_events!
84 "#{$!.message}\n\t#{$!.backtrace.join("\n\t")}"
93 @request_types ||= [:get, :put, :post, :delete]
97 @routes ||= Hash.new do |hash, key|
98 hash[key] = [] if request_types.include?(key)
103 @config ||= @default_config.dup
111 @default_config ||= {
113 :raise_errors => false,
114 :env => :development,
115 :root => File.dirname($0),
116 :default_static_mime_type => 'text/plain',
117 :default_params => { :format => 'html' }
121 def determine_route(verb, path)
122 routes[verb].eject { |r| r.match(path) } || routes[404]
125 def content_type_for(path)
126 ext = File.extname(path)[1..-1]
127 Sinatra.mime_types[ext] || config[:default_static_mime_type]
130 def serve_static_file(path)
131 path = Sinatra.config[:root] + '/public' + path
134 'Content-Type' => Array(content_type_for(path)),
135 'Content-Length' => Array(File.size(path))
137 [200, headers, File.read(path)]
142 request = Rack::Request.new(env)
144 if found = serve_static_file(request.path_info)
148 response = Rack::Response.new
149 route = determine_route(
150 request.request_method.downcase.to_sym,
153 context = EventContext.new(request, response, route.params)
156 result = context.instance_eval(&route.block)
157 context.status ||= route.default_status
158 context.body = Array(result.to_s)
161 raise e if config[:raise_errors]
162 route = Sinatra.routes[500]
164 context.body Array(context.instance_eval(&route.block))
169 def define_route(verb, path, &b)
170 routes[verb] << route = Route.new(path, &b)
174 def define_error(code, &b)
175 routes[code] = Error.new(code, &b)
180 URI_CHAR = '[^/?:,&#]'.freeze unless defined?(URI_CHAR)
181 PARAM = /:(#{URI_CHAR}+)/.freeze unless defined?(PARAM)
183 Result = Struct.new(:path, :block, :params, :default_status)
185 attr_reader :block, :path
187 def initialize(path, &b)
188 @path, @block = path, b
190 regex = path.to_s.gsub(PARAM) do
191 @param_keys << $1.intern
194 @param_keys << :format
195 @pattern = /^#{regex}(?:\.(#{URI_CHAR}+))?$/
199 return nil unless path =~ @pattern
200 params = @param_keys.zip($~.captures.compact.map(&:from_param)).to_hash
201 Result.new(@path, @block, include_format(params), 200)
204 def include_format(h)
205 h.delete(:format) unless h[:format]
206 Sinatra.config[:default_params].merge(h)
215 def initialize(code, &b)
216 @code, @block = code, b
229 paths.map { |path| Sinatra.define_route(:get, path, &b) }
232 def error(*codes, &b)
233 raise 'You must specify a block to assciate with an error' if b.nil?
234 codes.each { |code| Sinatra.define_error(code, &b) }
237 def mime_type(content_type, *exts)
238 exts.each { |ext| Sinatra::MIME_TYPES.merge(ext.to_s, content_type) }
241 Sinatra.setup_default_events!