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
265 def self.default_options
266 @@default_options ||= {
269 :env => :development,
271 :public => Dir.pwd + '/public'
276 self.class.default_options
281 OptionParser.new do |op|
282 op.on('-p port') { |port| default_options[:port] = port }
283 op.on('-e env') { |env| default_options[:env] = env }
288 @events = Hash.new { |hash, key| hash[key] = [] }
293 def define_event(method, path, options = {}, &b)
294 events[method] << event = Event.new(path, options, &b)
298 def define_layout(name=:layout, &b)
302 def define_error(code, options = {}, &b)
303 events[:errors][code] = Error.new(code, &b)
307 @static ||= Static.new
311 method = env['REQUEST_METHOD'].downcase.to_sym
312 e = static.invoke(env)
313 e ||= events[method].eject(&[:invoke, env])
314 e ||= (events[:errors][404] || basic_not_found).invoke(env)
326 '<h1>Internal Server Error</h1>'
331 @options ||= OpenStruct.new(default_options)
336 context = EventContext.new(
337 Rack::Request.new(env),
342 context.status(result.status)
343 returned = catch(:halt) do
344 [:complete, context.instance_eval(&result.block)]
346 body = returned.to_result(context)
347 context.body = String === body ? [*body] : body
350 raise e if options.raise_errors
351 env['sinatra.error'] = e
352 result = (events[:errors][500] || basic_error).invoke(env)
353 returned = catch(:halt) do
354 [:complete, context.instance_eval(&result.block)]
356 body = returned.to_result(context)
358 context.body = String === body ? [*body] : body
367 def get(path, options ={}, &b)
368 Sinatra.application.define_event(:get, path, options, &b)
371 def post(path, options ={}, &b)
372 Sinatra.application.define_event(:post, path, options, &b)
375 def put(path, options ={}, &b)
376 Sinatra.application.define_event(:put, path, options, &b)
379 def delete(path, options ={}, &b)
380 Sinatra.application.define_event(:delete, path, options, &b)
384 Sinatra::EventContext.class_eval(&b)
387 def error(code, options = {}, &b)
388 Sinatra.application.define_error(code, options, &b)
391 def layout(name = :layout, &b)
392 Sinatra.application.define_layout(name, &b)
395 def configures(*envs, &b)
396 yield if envs.include?(Sinatra.application.options.env) ||
399 alias :configure :configures
401 ### Misc Core Extensions
406 old_verbose, $VERBOSE = $VERBOSE, nil
409 $VERBOSE = old_verbose
416 # Converts +self+ to an escaped URI parameter value
417 # 'Foo Bar'.to_param # => 'Foo%20Bar'
422 # Converts +self+ from an escaped URI parameter value
423 # 'Foo%20Bar'.from_param # => 'Foo Bar'
433 map { |k,v| "#{k}=#{URI.escape(v)}" }.join('&')
437 self.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
441 reject { |k,v| !keys.include?(k) }
449 Proc.new { |*args| args.shift.__send__(self, *args) }
457 self.inject({}) { |h, (k, v)| h[k] = v; h }
461 Proc.new { |*args| args.shift.__send__(self[0], *(args + self[1..-1])) }
469 find { |e| result = block[e] and break result }
474 ### Core Extension results for throw :halt
477 def to_result(cx, *args)
478 cx.instance_eval(&self)
483 def to_result(cx, *args)
489 def to_result(cx, *args)
490 self.shift.to_result(cx, *self)
495 def to_result(cx, *args)
501 def to_result(cx, *args)
508 def to_result(cx, *args)
516 Sinatra.run if Sinatra.application.options.run
519 ENV['SINATRA_ENV'] = 'test' if $0 =~ /_test\.rb$/
520 Sinatra::Application.default_options.merge!(
521 :env => (ENV['SINATRA_ENV'] || 'development').to_sym
524 configures :development do
526 get '/sinatra_custom_images/:image.png' do
527 File.read(File.dirname(__FILE__) + "/../images/#{params[:image]}.png")
533 <body style='text-align: center; color: #888; font-family: Arial; font-size: 22px; margin: 20px'>
534 <h2>Sinatra doesn't know this diddy.</h2>
535 <img src='/sinatra_custom_images/404.png'></img>
542 @error = request.env['sinatra.error']
546 <style type="text/css" media="screen">
548 font-family: Verdana;
569 border-left: 2px solid #ddd;
578 <img src="/sinatra_custom_images/500.png" />
579 <div id="stacktrace">
580 <h1>#{@error.message}</h1>
581 <pre><code>#{@error.backtrace.join("\n")}</code></pre>