Events can have multiple routes
[sinatra.git] / lib / sinatra.rb
blobc847d384d91e846f465c25a5621cfa6d942cf5e8
1 require "rubygems"
2 require "rack"
4 class String
5   def to_param
6     URI.escape(self)
7   end
8   
9   def from_param
10     URI.unescape(self)
11   end
12 end
14 class Hash
15   def to_params
16     map { |k,v| "#{k}=#{URI.escape(v)}" }.join('&')
17   end
18   
19   def symbolize_keys
20     self.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
21   end
22   
23   def pass(*keys)
24     reject { |k,v| !keys.include?(k) }
25   end
26 end
28 class Symbol
29   def to_proc 
30     Proc.new { |*args| args.shift.__send__(self, *args) }
31   end
32 end
34 class Array
35   def to_hash
36     self.inject({}) { |h, (k, v)|  h[k] = v; h }
37   end
38   
39   def to_proc
40     Proc.new { |*args| args.shift.__send__(self[0], args + self[1..-1]) }
41   end
42 end
44 class Proc
45   def block
46     self
47   end
48 end
50 module Enumerable
51   def eject(&block)
52     find { |e| result = block[e] and break result }
53   end
54 end
56 module Sinatra
57   extend self
59   EventContext = Struct.new(:request, :response) do
60     def method_missing(name, *args)
61       if args.size == 1 && response.respond_to?("#{name}=")
62         response.send("#{name}=", args.first)
63       else
64         response.send(name, *args)
65       end
66     end
67   end
68   
69   def setup_default_events!
70     error 500 do
71       "#{$!.message}\n\t#{$!.backtrace.join("\n\t")}"
72     end
74     error 404 do
75       status 404
76       "<h1>Not Found</h1>"
77     end
78   end
79   
80   def request_types
81     @request_types ||= [:get, :put, :post, :delete]
82   end
83   
84   def routes
85     @routes ||= Hash.new do |hash, key|
86       hash[key] = [] if request_types.include?(key)
87     end
88   end
89   
90   def config
91     @config ||= @default_config
92   end
93   
94   def config=(c)
95     @config = c
96   end
97   
98   def default_config
99     @default_config ||= {
100       :run => true,
101       :raise_errors => false,
102       :env => :development
103     }
104   end
105   
106   def determine_route(verb, path)
107     routes[verb].eject { |r| r.match(path) } || routes[404]
108   end
109   
110   def call(env)
111     request = Rack::Request.new(env)
112     response = Rack::Response.new
113     route = determine_route(
114       request.request_method.downcase.to_sym, 
115       request.path_info
116     )
117     context = EventContext.new(request, response)
118     context.status = nil
119     begin
120       result = context.instance_eval(&route.block)
121       context.status ||= route.default_status
122       context.body = Array(result.to_s)
123       context.finish
124     rescue => e
125       raise e if config[:raise_errors]
126       route = Sinatra.routes[500]
127       context.status 500
128       context.body Array(context.instance_eval(&route.block))
129       context.finish
130     end
131   end
132   
133   def define_route(verb, path, &b)
134     routes[verb] << route = Route.new(path, &b)
135     route
136   end
137   
138   def define_error(code, &b)
139     routes[code] = Error.new(code, &b)
140   end
141   
142   class Route
143     
144     URI_CHAR = '[^/?:,&#]'.freeze unless defined?(URI_CHAR)
145     PARAM = /:(#{URI_CHAR}+)/.freeze unless defined?(PARAM)
146     
147     attr_reader :block, :path
148     
149     def initialize(path, &b)
150       @path, @block = path, b
151       @param_keys = []
152       regex = path.to_s.gsub(PARAM) do
153         @param_keys << $1.intern
154         "(#{URI_CHAR}+)"
155       end
156       @pattern = /^#{regex}$/
157       @struct = Struct.new(:path, :block, :params, :default_status)
158     end
159         
160     def match(path)
161       return nil unless path =~ @pattern
162       params = @param_keys.zip($~.captures.map(&:from_param)).to_hash
163       @struct.new(@path, @block, params, 200)
164     end
165     
166   end
167   
168   class Error
169     
170     attr_reader :block
171     
172     def initialize(code, &b)
173       @code, @block = code, b
174     end
175     
176     def default_status
177       @code
178     end
179         
180   end
181     
184 def get(*paths, &b)
185   paths.map { |path| Sinatra.define_route(:get, path, &b) }
188 def error(*codes, &b)
189   raise 'You must specify a block to assciate with an error' if b.nil?
190   codes.each { |code| Sinatra.define_error(code, &b) }
193 Sinatra.setup_default_events!