diff --git a/lib/sinatra.rb b/lib/sinatra.rb
index 279a30e5..dacc1503 100644
--- a/lib/sinatra.rb
+++ b/lib/sinatra.rb
@@ -10,378 +10,15 @@ end
require "rack"
+require File.dirname(__FILE__) + '/sinatra/application'
+require File.dirname(__FILE__) + '/sinatra/event_context'
+require File.dirname(__FILE__) + '/sinatra/route'
+require File.dirname(__FILE__) + '/sinatra/error'
require File.dirname(__FILE__) + '/sinatra/mime_types'
+require File.dirname(__FILE__) + '/sinatra/core_ext'
require File.dirname(__FILE__) + '/sinatra/halt_results'
require File.dirname(__FILE__) + '/sinatra/logger'
-def silence_warnings
- old_verbose, $VERBOSE = $VERBOSE, nil
- yield
-ensure
- $VERBOSE = old_verbose
-end
-
-class String
- def to_param
- URI.escape(self)
- end
-
- def from_param
- URI.unescape(self)
- end
-end
-
-class Hash
- def to_params
- map { |k,v| "#{k}=#{URI.escape(v)}" }.join('&')
- end
-
- def symbolize_keys
- self.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
- end
-
- def pass(*keys)
- reject { |k,v| !keys.include?(k) }
- end
-end
-
-class Symbol
- def to_proc
- Proc.new { |*args| args.shift.__send__(self, *args) }
- end
-end
-
-class Array
- def to_hash
- self.inject({}) { |h, (k, v)| h[k] = v; h }
- end
-
- def to_proc
- Proc.new { |*args| args.shift.__send__(self[0], args + self[1..-1]) }
- end
-end
-
-class Proc
- def block
- self
- end
-end
-
-module Enumerable
- def eject(&block)
- find { |e| result = block[e] and break result }
- end
-end
-
-module Sinatra
- extend self
-
- attr_accessor :logger
-
- def run
-
- begin
- puts "== Sinatra has taken the stage on port #{Sinatra.config[:port]} for #{Sinatra.config[:env]}"
- require 'pp'
- Rack::Handler::Mongrel.run(Sinatra, :Port => Sinatra.config[:port]) do |server|
- trap(:INT) do
- server.stop
- puts "\n== Sinatra has ended his set (crowd applauds)"
- end
- end
- rescue Errno::EADDRINUSE => e
- puts "== Someone is already performing on port #{Sinatra.config[:port]}!"
- end
-
- end
-
- class EventContext
-
- attr_reader :request, :response, :route_params
-
- def logger
- Sinatra.logger
- end
-
- def initialize(request, response, route_params)
- @request, @response, @route_params =
- request, response, route_params
- end
-
- def params
- @params ||= request.params.merge(route_params).symbolize_keys
- end
-
- def complete(b)
- self.instance_eval(&b)
- end
-
- # redirect to another url It can be like /foo/bar
- # for redirecting within your same app. Or it can
- # be a fully qualified url to another site.
- def redirect(url)
- logger.info "Redirecting to: #{url}"
- status(302)
- headers.merge!('Location' => url)
- return ''
- end
-
- def method_missing(name, *args)
- if args.size == 1 && response.respond_to?("#{name}=")
- response.send("#{name}=", args.first)
- else
- response.send(name, *args)
- end
- end
-
- end
-
- def setup_logger
- self.logger = Sinatra::Logger.new(
- config[:root] + "/#{Sinatra.config[:env]}.log"
- )
- end
-
- def setup_default_events!
- error 500 do
- "
#{$!.message}
#{$!.backtrace.join("
")}"
- end
-
- error 404 do
- "Not Found
"
- end
- end
-
- def request_types
- @request_types ||= [:get, :put, :post, :delete]
- end
-
- def routes
- @routes ||= Hash.new do |hash, key|
- hash[key] = [] if request_types.include?(key)
- end
- end
-
- def filters
- unless @filters
- @filters = Hash.new do |hash, key|
- hash[key] = Hash.new { |hash, key| hash[key] = [] }
- end
- end
- @filters
- end
-
- def config
- @config ||= default_config.dup
- end
-
- def config=(c)
- @config = c
- end
-
- def development?
- config[:env] == :development
- end
-
- def default_config
- @default_config ||= {
- :run => true,
- :port => 4567,
- :raise_errors => false,
- :env => :development,
- :root => Dir.pwd,
- :default_static_mime_type => 'text/plain',
- :default_params => { :format => 'html' }
- }
- end
-
- def determine_route(verb, path)
- routes[verb].eject { |r| r.match(path) } || routes[404]
- end
-
- def content_type_for(path)
- ext = File.extname(path)[1..-1]
- Sinatra.mime_types[ext] || config[:default_static_mime_type]
- end
-
- def serve_static_file(path)
- path = Sinatra.config[:root] + '/public' + path
- if File.file?(path)
- headers = {
- 'Content-Type' => Array(content_type_for(path)),
- 'Content-Length' => Array(File.size(path))
- }
- [200, headers, File.read(path)]
- end
- end
-
- def call(env)
-
- reload! if Sinatra.development?
-
- time = Time.now
-
- request = Rack::Request.new(env)
-
- if found = serve_static_file(request.path_info)
- log_request_and_response(time, request, Rack::Response.new(found))
- return found
- end
-
- response = Rack::Response.new
- route = determine_route(
- request.request_method.downcase.to_sym,
- request.path_info
- )
- context = EventContext.new(request, response, route.params)
- context.status = nil
- begin
- context = handle_with_filters(route.groups, context, &route.block)
- context.status ||= route.default_status
- rescue => e
- raise e if config[:raise_errors]
- route = Sinatra.routes[500]
- context.status 500
- context.body Array(context.instance_eval(&route.block))
- ensure
- log_request_and_response(time, request, response)
- logger.flush
- end
-
- context.finish
- end
-
- def define_route(verb, path, options = {}, &b)
- routes[verb] << route = Route.new(path, Array(options[:groups]), &b)
- route
- end
-
- def define_error(code, &b)
- routes[code] = Error.new(code, &b)
- end
-
- def define_filter(type, group, &b)
- filters[type][group] << b
- end
-
- def reset!
- self.config = nil
- routes.clear
- filters.clear
- setup_default_events!
- end
-
- def reload!
- reset!
- self.config[:reloading] = true
- load $0
- self.config[:reloading] = false
- end
-
- protected
-
- def log_request_and_response(time, request, response)
- now = Time.now
-
- # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
- # lilith.local - - [07/Aug/2006 23:58:02] "GET / HTTP/1.1" 500 -
- # %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
- logger.info %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n} %
- [
- request.env["REMOTE_ADDR"] || "-",
- request.env["REMOTE_USER"] || "-",
- now.strftime("%d/%b/%Y %H:%M:%S"),
- request.env["REQUEST_METHOD"],
- request.env["PATH_INFO"],
- request.env["QUERY_STRING"].empty? ?
- "" :
- "?" + request.env["QUERY_STRING"],
- request.env["HTTP_VERSION"],
- response.status.to_s[0..3].to_i,
- (response.body.length.zero? ? "-" : response.body.length.to_s),
- now - time
- ]
- end
-
- def handle_with_filters(groups, cx, &b)
- caught = catch(:halt) do
- filters_for(:before, groups).each { |x| cx.instance_eval(&x) }
- [:complete, b]
- end
- caught = catch(:halt) do
- caught.to_result(cx)
- end
- result = caught.to_result(cx) if caught
- filters_for(:after, groups).each { |x| cx.instance_eval(&x) }
- cx.body Array(result.to_s)
- cx
- end
-
- def filters_for(type, groups)
- filters[type][:all] + groups.inject([]) do |m, g|
- m + filters[type][g]
- end
- end
-
- class Route
-
- URI_CHAR = '[^/?:,]'.freeze unless defined?(URI_CHAR)
- PARAM = /:(#{URI_CHAR}+)/.freeze unless defined?(PARAM)
-
- attr_reader :block, :path
-
- def initialize(path, groups = :all, &b)
- @path, @groups, @block = path, Array(groups), b
- @param_keys = []
- @struct = Struct.new(:path, :groups, :block, :params, :default_status)
- regex = path.to_s.gsub(PARAM) do
- @param_keys << $1.intern
- "(#{URI_CHAR}+)"
- end
- if path =~ /:format$/
- @pattern = /^#{regex}$/
- else
- @param_keys << :format
- @pattern = /^#{regex}(?:\.(#{URI_CHAR}+))?$/
- end
- end
-
- def match(path)
- return nil unless path =~ @pattern
- params = @param_keys.zip($~.captures.compact.map(&:from_param)).to_hash
- @struct.new(@path, @groups, @block, include_format(params), 200)
- end
-
- def include_format(h)
- h.delete(:format) unless h[:format]
- Sinatra.config[:default_params].merge(h)
- end
-
- def pretty_print(pp)
- pp.text "{Route: #{@pattern} : [#{@param_keys.map(&:inspect).join(",")}] : #{@groups.join(",")} }"
- end
-
- end
-
- class Error
-
- attr_reader :block
-
- def initialize(code, &b)
- @code, @block = code, b
- end
-
- def default_status
- @code
- end
-
- def params; {}; end
-
- def groups; []; end
- end
-
-end
-
def get(*paths, &b)
options = Hash === paths.last ? paths.pop : {}
paths.map { |path| Sinatra.define_route(:get, path, options, &b) }
diff --git a/lib/sinatra/application.rb b/lib/sinatra/application.rb
new file mode 100644
index 00000000..60fefb83
--- /dev/null
+++ b/lib/sinatra/application.rb
@@ -0,0 +1,218 @@
+module Sinatra
+ extend self
+
+ def method_missing(name, *args, &b)
+ Application.send(name, *args, &b)
+ end
+
+ module Application
+ extend self
+
+ attr_accessor :logger
+
+ def run
+
+ begin
+ puts "== Sinatra has taken the stage on port #{Sinatra.config[:port]} for #{Sinatra.config[:env]}"
+ require 'pp'
+ Rack::Handler::Mongrel.run(Sinatra, :Port => Sinatra.config[:port]) do |server|
+ trap(:INT) do
+ server.stop
+ puts "\n== Sinatra has ended his set (crowd applauds)"
+ end
+ end
+ rescue Errno::EADDRINUSE => e
+ puts "== Someone is already performing on port #{Sinatra.config[:port]}!"
+ end
+
+ end
+
+ def setup_logger
+ self.logger = Sinatra::Logger.new(
+ config[:root] + "/#{Sinatra.config[:env]}.log"
+ )
+ end
+
+ def setup_default_events!
+ error 500 do
+ "#{$!.message}
#{$!.backtrace.join("
")}"
+ end
+
+ error 404 do
+ "Not Found
"
+ end
+ end
+
+ def request_types
+ @request_types ||= [:get, :put, :post, :delete]
+ end
+
+ def routes
+ @routes ||= Hash.new do |hash, key|
+ hash[key] = [] if request_types.include?(key)
+ end
+ end
+
+ def filters
+ unless @filters
+ @filters = Hash.new do |hash, key|
+ hash[key] = Hash.new { |hash, key| hash[key] = [] }
+ end
+ end
+ @filters
+ end
+
+ def config
+ @config ||= default_config.dup
+ end
+
+ def config=(c)
+ @config = c
+ end
+
+ def development?
+ config[:env] == :development
+ end
+
+ def default_config
+ @default_config ||= {
+ :run => true,
+ :port => 4567,
+ :raise_errors => false,
+ :env => :development,
+ :root => Dir.pwd,
+ :default_static_mime_type => 'text/plain',
+ :default_params => { :format => 'html' }
+ }
+ end
+
+ def determine_route(verb, path)
+ routes[verb].eject { |r| r.match(path) } || routes[404]
+ end
+
+ def content_type_for(path)
+ ext = File.extname(path)[1..-1]
+ Sinatra.mime_types[ext] || config[:default_static_mime_type]
+ end
+
+ def serve_static_file(path)
+ path = Sinatra.config[:root] + '/public' + path
+ if File.file?(path)
+ headers = {
+ 'Content-Type' => Array(content_type_for(path)),
+ 'Content-Length' => Array(File.size(path))
+ }
+ [200, headers, File.read(path)]
+ end
+ end
+
+ def call(env)
+
+ reload! if Sinatra.development?
+
+ time = Time.now
+
+ request = Rack::Request.new(env)
+
+ if found = serve_static_file(request.path_info)
+ log_request_and_response(time, request, Rack::Response.new(found))
+ return found
+ end
+
+ response = Rack::Response.new
+ route = determine_route(
+ request.request_method.downcase.to_sym,
+ request.path_info
+ )
+ context = EventContext.new(request, response, route.params)
+ context.status = nil
+ begin
+ context = handle_with_filters(route.groups, context, &route.block)
+ context.status ||= route.default_status
+ rescue => e
+ raise e if config[:raise_errors]
+ route = Sinatra.routes[500]
+ context.status 500
+ context.body Array(context.instance_eval(&route.block))
+ ensure
+ log_request_and_response(time, request, response)
+ logger.flush
+ end
+
+ context.finish
+ end
+
+ def define_route(verb, path, options = {}, &b)
+ routes[verb] << route = Route.new(path, Array(options[:groups]), &b)
+ route
+ end
+
+ def define_error(code, &b)
+ routes[code] = Error.new(code, &b)
+ end
+
+ def define_filter(type, group, &b)
+ filters[type][group] << b
+ end
+
+ def reset!
+ self.config = nil
+ routes.clear
+ filters.clear
+ setup_default_events!
+ end
+
+ def reload!
+ reset!
+ self.config[:reloading] = true
+ load $0
+ self.config[:reloading] = false
+ end
+
+ protected
+
+ def log_request_and_response(time, request, response)
+ now = Time.now
+
+ # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
+ # lilith.local - - [07/Aug/2006 23:58:02] "GET / HTTP/1.1" 500 -
+ # %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
+ logger.info %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n} %
+ [
+ request.env["REMOTE_ADDR"] || "-",
+ request.env["REMOTE_USER"] || "-",
+ now.strftime("%d/%b/%Y %H:%M:%S"),
+ request.env["REQUEST_METHOD"],
+ request.env["PATH_INFO"],
+ request.env["QUERY_STRING"].empty? ?
+ "" :
+ "?" + request.env["QUERY_STRING"],
+ request.env["HTTP_VERSION"],
+ response.status.to_s[0..3].to_i,
+ (response.body.length.zero? ? "-" : response.body.length.to_s),
+ now - time
+ ]
+ end
+
+ def handle_with_filters(groups, cx, &b)
+ caught = catch(:halt) do
+ filters_for(:before, groups).each { |x| cx.instance_eval(&x) }
+ [:complete, b]
+ end
+ caught = catch(:halt) do
+ caught.to_result(cx)
+ end
+ result = caught.to_result(cx) if caught
+ filters_for(:after, groups).each { |x| cx.instance_eval(&x) }
+ cx.body Array(result.to_s)
+ cx
+ end
+
+ def filters_for(type, groups)
+ filters[type][:all] + groups.inject([]) do |m, g|
+ m + filters[type][g]
+ end
+ end
+ end
+
+end
diff --git a/lib/sinatra/core_ext.rb b/lib/sinatra/core_ext.rb
new file mode 100644
index 00000000..48ad4639
--- /dev/null
+++ b/lib/sinatra/core_ext.rb
@@ -0,0 +1,58 @@
+def silence_warnings
+ old_verbose, $VERBOSE = $VERBOSE, nil
+ yield
+ensure
+ $VERBOSE = old_verbose
+end
+
+class String
+ def to_param
+ URI.escape(self)
+ end
+
+ def from_param
+ URI.unescape(self)
+ end
+end
+
+class Hash
+ def to_params
+ map { |k,v| "#{k}=#{URI.escape(v)}" }.join('&')
+ end
+
+ def symbolize_keys
+ self.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
+ end
+
+ def pass(*keys)
+ reject { |k,v| !keys.include?(k) }
+ end
+end
+
+class Symbol
+ def to_proc
+ Proc.new { |*args| args.shift.__send__(self, *args) }
+ end
+end
+
+class Array
+ def to_hash
+ self.inject({}) { |h, (k, v)| h[k] = v; h }
+ end
+
+ def to_proc
+ Proc.new { |*args| args.shift.__send__(self[0], args + self[1..-1]) }
+ end
+end
+
+class Proc
+ def block
+ self
+ end
+end
+
+module Enumerable
+ def eject(&block)
+ find { |e| result = block[e] and break result }
+ end
+end
diff --git a/lib/sinatra/error.rb b/lib/sinatra/error.rb
new file mode 100644
index 00000000..73235a1e
--- /dev/null
+++ b/lib/sinatra/error.rb
@@ -0,0 +1,17 @@
+class Error
+
+ attr_reader :block
+
+ def initialize(code, &b)
+ @code, @block = code, b
+ end
+
+ def default_status
+ @code
+ end
+
+ def params; {}; end
+
+ def groups; []; end
+
+end
diff --git a/lib/sinatra/event_context.rb b/lib/sinatra/event_context.rb
new file mode 100644
index 00000000..f49fdcc2
--- /dev/null
+++ b/lib/sinatra/event_context.rb
@@ -0,0 +1,40 @@
+class Sinatra::EventContext
+
+ attr_reader :request, :response, :route_params
+
+ def logger
+ Sinatra.logger
+ end
+
+ def initialize(request, response, route_params)
+ @request, @response, @route_params =
+ request, response, route_params
+ end
+
+ def params
+ @params ||= request.params.merge(route_params).symbolize_keys
+ end
+
+ def complete(b)
+ self.instance_eval(&b)
+ end
+
+ # redirect to another url It can be like /foo/bar
+ # for redirecting within your same app. Or it can
+ # be a fully qualified url to another site.
+ def redirect(url)
+ logger.info "Redirecting to: #{url}"
+ status(302)
+ headers.merge!('Location' => url)
+ return ''
+ end
+
+ def method_missing(name, *args)
+ if args.size == 1 && response.respond_to?("#{name}=")
+ response.send("#{name}=", args.first)
+ else
+ response.send(name, *args)
+ end
+ end
+
+end
diff --git a/lib/sinatra/route.rb b/lib/sinatra/route.rb
new file mode 100644
index 00000000..a74e1cfe
--- /dev/null
+++ b/lib/sinatra/route.rb
@@ -0,0 +1,39 @@
+class Sinatra::Route
+
+ URI_CHAR = '[^/?:,]'.freeze unless defined?(URI_CHAR)
+ PARAM = /:(#{URI_CHAR}+)/.freeze unless defined?(PARAM)
+
+ attr_reader :block, :path
+
+ def initialize(path, groups = :all, &b)
+ @path, @groups, @block = path, Array(groups), b
+ @param_keys = []
+ @struct = Struct.new(:path, :groups, :block, :params, :default_status)
+ regex = path.to_s.gsub(PARAM) do
+ @param_keys << $1.intern
+ "(#{URI_CHAR}+)"
+ end
+ if path =~ /:format$/
+ @pattern = /^#{regex}$/
+ else
+ @param_keys << :format
+ @pattern = /^#{regex}(?:\.(#{URI_CHAR}+))?$/
+ end
+ end
+
+ def match(path)
+ return nil unless path =~ @pattern
+ params = @param_keys.zip($~.captures.compact.map(&:from_param)).to_hash
+ @struct.new(@path, @groups, @block, include_format(params), 200)
+ end
+
+ def include_format(h)
+ h.delete(:format) unless h[:format]
+ Sinatra.config[:default_params].merge(h)
+ end
+
+ def pretty_print(pp)
+ pp.text "{Route: #{@pattern} : [#{@param_keys.map(&:inspect).join(",")}] : #{@groups.join(",")} }"
+ end
+
+end