throw :halt
[sinatra.git] / lib / sinatra.rb
blobf1aae7332d5cd68aa6890c5b8a5aa1e72d18aaea
1 require 'rubygems'
2 require 'rack'
4 class Class
5   def dslify_writter(*syms)
6     syms.each do |sym|
7       class_eval <<-end_eval
8         def #{sym}(v=nil)
9           self.send "#{sym}=", v if v
10           v
11         end
12       end_eval
13     end
14   end
15 end
17 module Sinatra
18   extend self
20   Result = Struct.new(:block, :params)
21   
22   def application
23     @app ||= Application.new
24   end
25   
26   def application=(app)
27     @app = app
28   end
29       
30   class Event
32     URI_CHAR = '[^/?:,&#]'.freeze unless defined?(URI_CHAR)
33     PARAM = /:(#{URI_CHAR}+)/.freeze unless defined?(PARAM)
34     
35     attr_reader :path, :block, :param_keys, :pattern
36     
37     def initialize(path, &b)
38       @path = path
39       @block = b
40       @param_keys = []
41       regex = @path.to_s.gsub(PARAM) do
42         @param_keys << $1.intern
43         "(#{URI_CHAR}+)"
44       end
45       @pattern = /^#{regex}$/
46     end
47         
48     def invoke(env)
49       return unless pattern =~ env['PATH_INFO'].squeeze('/')
50       params = param_keys.zip($~.captures.map(&:from_param)).to_hash
51       Result.new(block, params)
52     end
53     
54   end
55   
56   class EventContext
57     
58     module ResponseHelpers
60       def redirect(path)
61         throw :halt, NotFound.new(path)
62       end
64     end
65     
66     include ResponseHelpers
67     
68     attr_accessor :request, :response
69     
70     dslify_writter :status, :body
71     
72     def initialize(request, response, route_params)
73       @request = request
74       @response = response
75       @route_params = route_params
76       @response.body = nil
77     end
78     
79     def params
80       @params ||= @route_params.merge(@request.params).symbolize_keys
81     end
82     
83     def complete(returned)
84       @response.body ||= returned
85     end
86     
87     def method_missing(name, *args, &b)
88       @response.send(name, *args, &b)
89     end
90     
91   end
92   
93   class NotFound
94     def initialize(path)
95       @path = path
96     end
97     
98     def to_result(cx, *args)
99       cx.status(302)
100       cx.header.merge!('Location' => @path)
101       cx.body('')
102     end
103   end
104   
105   class Application
106     
107     attr_reader :events
108     
109     def initialize
110       @events = Hash.new { |hash, key| hash[key] = [] }
111     end
112     
113     def define_event(method, path, &b)
114       events[method] << event = Event.new(path, &b)
115       event
116     end
117     
118     def lookup(env)
119       events[env['REQUEST_METHOD'].downcase.to_sym].eject(&[:invoke, env])
120     end
121     
122     def call(env)
123       return [404, {}, 'Not Found'] unless result = lookup(env)
124       context = EventContext.new(
125         Rack::Request.new(env), 
126         Rack::Response.new,
127         result.params
128       )
129       returned = catch(:halt) do
130         [:complete, context.instance_eval(&result.block)]
131       end
132       result = returned.to_result(context)
133       context.body = String === result ? [*result] : result
134       context.finish
135     end
136         
137   end
138   
141 def get(path, &b)
142   Sinatra.application.define_event(:get, path, &b)
145 def post(path, &b)
146   Sinatra.application.define_event(:post, path, &b)
149 def put(path, &b)
150   Sinatra.application.define_event(:put, path, &b)
153 def delete(path, &b)
154   Sinatra.application.define_event(:delete, path, &b)
157 def helpers(&b)
158   Sinatra::EventContext.class_eval(&b)
161 ### Misc Core Extensions
163 module Kernel
165   def silence_warnings
166     old_verbose, $VERBOSE = $VERBOSE, nil
167     yield
168   ensure
169     $VERBOSE = old_verbose
170   end
174 class String
176   # Converts +self+ to an escaped URI parameter value
177   #   'Foo Bar'.to_param # => 'Foo%20Bar'
178   def to_param
179     URI.escape(self)
180   end
181   
182   # Converts +self+ from an escaped URI parameter value
183   #   'Foo%20Bar'.from_param # => 'Foo Bar'
184   def from_param
185     URI.unescape(self)
186   end
187   
190 class Hash
191   
192   def to_params
193     map { |k,v| "#{k}=#{URI.escape(v)}" }.join('&')
194   end
195   
196   def symbolize_keys
197     self.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
198   end
199   
200   def pass(*keys)
201     reject { |k,v| !keys.include?(k) }
202   end
203   
206 class Symbol
207   
208   def to_proc 
209     Proc.new { |*args| args.shift.__send__(self, *args) }
210   end
211   
214 class Array
215   
216   def to_hash
217     self.inject({}) { |h, (k, v)|  h[k] = v; h }
218   end
219   
220   def to_proc
221     Proc.new { |*args| args.shift.__send__(self[0], *(args + self[1..-1])) }
222   end
223   
226 module Enumerable
227   
228   def eject(&block)
229     find { |e| result = block[e] and break result }
230   end
231   
234 ### Core Extension results for throw :halt
236 class Proc
237   def to_result(cx, *args)
238     cx.instance_eval(&self)
239   end
242 class String
243   def to_result(cx, *args)
244     cx.body self
245   end
248 class Array
249   def to_result(cx, *args)
250     self.shift.to_result(cx, *self)
251   end
254 class Symbol
255   def to_result(cx, *args)
256     cx.send(self, *args)
257   end
260 class Fixnum
261   def to_result(cx, *args)
262     cx.status self
263     cx.body args.first
264   end