5 require 'swiftcore/swiftiplied_mongrel'
6 puts "Using Swiftiplied Mongrel"
8 require 'swiftcore/evented_mongrel'
9 puts "Using Evented Mongrel"
16 def dslify_writter(*syms)
18 class_eval <<-end_eval
20 self.send "#{sym}=", v if v
31 Result = Struct.new(:block, :params, :status) unless defined?(Result)
34 @app ||= Application.new
42 application.options.port
46 application.options.env
50 Rack::CommonLogger.new(application)
56 puts "== Sinatra has taken the stage on port #{port} for #{env}"
58 Rack::Handler::Mongrel.run(build_application, :Port => port) do |server|
61 puts "\n== Sinatra has ended his set (crowd applauds)"
64 rescue Errno::EADDRINUSE => e
65 puts "== Someone is already performing on port #{port}!"
72 URI_CHAR = '[^/?:,&#\.]'.freeze unless defined?(URI_CHAR)
73 PARAM = /:(#{URI_CHAR}+)/.freeze unless defined?(PARAM)
75 attr_reader :path, :block, :param_keys, :pattern, :options
77 def initialize(path, options = {}, &b)
82 regex = @path.to_s.gsub(PARAM) do
83 @param_keys << $1.intern
87 regex.gsub!('*', SPLAT.to_s)
89 @pattern = /^#{regex}$/
94 return unless env['HTTP_USER_AGENT'] =~ options[:agent]
96 return unless pattern =~ env['PATH_INFO'].squeeze('/')
97 params = param_keys.zip($~.captures.map(&:from_param)).to_hash
98 Result.new(block, params, 200)
105 attr_reader :code, :block
107 def initialize(code, &b)
108 @code, @block = code, b
112 Result.new(block, {}, 404)
120 return unless File.file?(
121 Sinatra.application.options.public + env['PATH_INFO']
123 Result.new(block, {}, 200)
128 send_file Sinatra.application.options.public +
129 request.env['PATH_INFO']
135 module ResponseHelpers
138 throw :halt, Redirect.new(path)
141 def send_file(filename)
142 throw :halt, SendFile.new(filename)
147 module RenderingHelpers
149 def render(content, options={})
150 template = resolve_template(content, options)
151 @content = _evaluate_render(template)
152 layout = resolve_layout(options[:layout], options)
153 @content = _evaluate_render(layout) if layout
159 def _evaluate_render(content, options={})
162 instance_eval(%Q{"#{content}"})
164 instance_eval(&content)
166 instance_eval(%Q{"#{content.read}"})
170 def resolve_template(content, options={})
175 File.new(filename_for(content, options))
179 def resolve_layout(name, options={})
180 return if name == false
181 if layout = layouts[name || :layout]
184 if File.file?(filename = filename_for(name, options))
189 def filename_for(name, options={})
190 (options[:views_directory] || 'views') + "/#{name}.#{ext}"
198 Sinatra.application.layouts
205 include ResponseHelpers
206 include RenderingHelpers
208 attr_accessor :request, :response
210 dslify_writter :status, :body
212 def initialize(request, response, route_params)
215 @route_params = route_params
220 @params ||= @route_params.merge(@request.params).symbolize_keys
227 def complete(returned)
228 @response.body || returned
233 def method_missing(name, *args, &b)
234 @response.send(name, *args, &b)
244 def to_result(cx, *args)
246 cx.header.merge!('Location' => @path)
252 def initialize(filename)
256 def to_result(cx, *args)
257 cx.body = File.read(@filename)
263 attr_reader :events, :layouts, :default_options, :filters
266 def self.default_options
267 @@default_options ||= {
270 :env => :development,
272 :public => Dir.pwd + '/public'
277 self.class.default_options
282 OptionParser.new do |op|
283 op.on('-p port') { |port| default_options[:port] = port }
284 op.on('-e env') { |env| default_options[:env] = env }
289 @events = Hash.new { |hash, key| hash[key] = [] }
290 @filters = Hash.new { |hash, key| hash[key] = [] }
295 def define_event(method, path, options = {}, &b)
296 events[method] << event = Event.new(path, options, &b)
300 def define_layout(name=:layout, &b)
304 def define_error(code, options = {}, &b)
305 events[:errors][code] = Error.new(code, &b)
308 def define_filter(type, &b)
309 filters[:before] << b
313 @static ||= Static.new
317 method = env['REQUEST_METHOD'].downcase.to_sym
318 e = static.invoke(env)
319 e ||= events[method].eject(&[:invoke, env])
320 e ||= (events[:errors][404] || basic_not_found).invoke(env)
332 '<h1>Internal Server Error</h1>'
337 @options ||= OpenStruct.new(default_options)
342 context = EventContext.new(
343 Rack::Request.new(env),
348 context.status(result.status)
349 returned = catch(:halt) do
350 filters[:before].each { |f| context.instance_eval(&f) }
351 [:complete, context.instance_eval(&result.block)]
353 body = returned.to_result(context)
354 context.body = String === body ? [*body] : body
357 raise e if options.raise_errors
358 env['sinatra.error'] = e
359 result = (events[:errors][500] || basic_error).invoke(env)
360 returned = catch(:halt) do
361 [:complete, context.instance_eval(&result.block)]
363 body = returned.to_result(context)
365 context.body = String === body ? [*body] : body
374 def get(path, options ={}, &b)
375 Sinatra.application.define_event(:get, path, options, &b)
378 def post(path, options ={}, &b)
379 Sinatra.application.define_event(:post, path, options, &b)
382 def put(path, options ={}, &b)
383 Sinatra.application.define_event(:put, path, options, &b)
386 def delete(path, options ={}, &b)
387 Sinatra.application.define_event(:delete, path, options, &b)
391 Sinatra.application.define_filter(:before, &b)
395 Sinatra::EventContext.class_eval(&b)
398 def error(code, options = {}, &b)
399 Sinatra.application.define_error(code, options, &b)
402 def layout(name = :layout, &b)
403 Sinatra.application.define_layout(name, &b)
406 def configures(*envs, &b)
407 yield if envs.include?(Sinatra.application.options.env) ||
410 alias :configure :configures
412 ### Misc Core Extensions
417 old_verbose, $VERBOSE = $VERBOSE, nil
420 $VERBOSE = old_verbose
427 # Converts +self+ to an escaped URI parameter value
428 # 'Foo Bar'.to_param # => 'Foo%20Bar'
433 # Converts +self+ from an escaped URI parameter value
434 # 'Foo%20Bar'.from_param # => 'Foo Bar'
444 map { |k,v| "#{k}=#{URI.escape(v)}" }.join('&')
448 self.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
452 reject { |k,v| !keys.include?(k) }
460 Proc.new { |*args| args.shift.__send__(self, *args) }
468 self.inject({}) { |h, (k, v)| h[k] = v; h }
472 Proc.new { |*args| args.shift.__send__(self[0], *(args + self[1..-1])) }
480 find { |e| result = block[e] and break result }
485 ### Core Extension results for throw :halt
488 def to_result(cx, *args)
489 cx.instance_eval(&self)
494 def to_result(cx, *args)
500 def to_result(cx, *args)
501 self.shift.to_result(cx, *self)
506 def to_result(cx, *args)
512 def to_result(cx, *args)
519 def to_result(cx, *args)
527 Sinatra.run if Sinatra.application.options.run
530 configures :development do
532 get '/sinatra_custom_images/:image.png' do
533 File.read(File.dirname(__FILE__) + "/../images/#{params[:image]}.png")
539 <body style='text-align: center; color: #888; font-family: Arial; font-size: 22px; margin: 20px'>
540 <h2>Sinatra doesn't know this diddy.</h2>
541 <img src='/sinatra_custom_images/404.png'></img>
548 @error = request.env['sinatra.error']
552 <style type="text/css" media="screen">
554 font-family: Verdana;
575 border-left: 2px solid #ddd;
584 <img src="/sinatra_custom_images/500.png" />
585 <div id="stacktrace">
586 <h1>#{@error.message}</h1>
587 <pre><code>#{@error.backtrace.join("\n")}</code></pre>