broke out render evaluation for plugin-gems to override
[sinatra.git] / lib / sinatra.rb
blobabb523dfbe2b00164b68aee00378da520a0787eb
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, Redirect.new(path)
62       end
64     end
65     include ResponseHelpers
66     
67     module RenderingHelpers
68       
69       def render(content, options={})
70         @content = _evaluate_render(%Q{"#{content}"})
71         layout = resolve_layout(options[:layout])
72         @content = _evaluate_render(layout) if layout
73         @content
74       end
75       
76       private
77         
78         def _evaluate_render(content, options={})
79           case content
80           when String
81             instance_eval(content)
82           when Proc
83             instance_eval(&content)
84           end
85         end
86       
87         def resolve_layout(name, options={})
88           return if name == false
89           layouts[name || :layout]
90         end
92         def layouts
93           Sinatra.application.layouts
94         end
95       
96     end
97     include RenderingHelpers
98     
99     
100     attr_accessor :request, :response
101     
102     dslify_writter :status
103     
104     def initialize(request, response, route_params)
105       @request = request
106       @response = response
107       @route_params = route_params
108       @response.body = nil
109     end
110     
111     def params
112       @params ||= @route_params.merge(@request.params).symbolize_keys
113     end
114     
115     def body(content)
116       throw :halt, content
117     end
118     
119     def complete(returned)
120       @response.body || returned
121     end
122     
123     private
125       def _body=(content)
126         @response.body = content
127       end
128     
129       def method_missing(name, *args, &b)
130         raise NoMethodError.new('body=') if name == :body=
131         @response.send(name, *args, &b)
132       end
133     
134   end
135   
136   class Redirect
137     def initialize(path)
138       @path = path
139     end
140     
141     def to_result(cx, *args)
142       cx.status(302)
143       cx.header.merge!('Location' => @path)
144       cx.send :_body=, ''
145     end
146   end
147     
148   class Application
149     
150     attr_reader :events, :layouts
151     
152     def initialize
153       @events = Hash.new { |hash, key| hash[key] = [] }
154       @layouts = Hash.new
155     end
156     
157     def define_event(method, path, &b)
158       events[method] << event = Event.new(path, &b)
159       event
160     end
161     
162     def define_layout(name=:layout, &b)
163       layouts[name] = b
164     end
165     
166     def lookup(env)
167       events[env['REQUEST_METHOD'].downcase.to_sym].eject(&[:invoke, env])
168     end
169     
170     def call(env)
171       return [404, {}, 'Not Found'] unless result = lookup(env)
172       context = EventContext.new(
173         Rack::Request.new(env), 
174         Rack::Response.new,
175         result.params
176       )
177       returned = catch(:halt) do
178         [:complete, context.instance_eval(&result.block)]
179       end
180       result = returned.to_result(context)
181       context.send :_body=, String === result ? [*result] : result
182       context.finish
183     end
184         
185   end
186   
189 def get(path, &b)
190   Sinatra.application.define_event(:get, path, &b)
193 def post(path, &b)
194   Sinatra.application.define_event(:post, path, &b)
197 def put(path, &b)
198   Sinatra.application.define_event(:put, path, &b)
201 def delete(path, &b)
202   Sinatra.application.define_event(:delete, path, &b)
205 def helpers(&b)
206   Sinatra::EventContext.class_eval(&b)
209 def layout(name = :layout, &b)
210   Sinatra.application.define_layout(name, &b)
213 ### Misc Core Extensions
215 module Kernel
217   def silence_warnings
218     old_verbose, $VERBOSE = $VERBOSE, nil
219     yield
220   ensure
221     $VERBOSE = old_verbose
222   end
226 class String
228   # Converts +self+ to an escaped URI parameter value
229   #   'Foo Bar'.to_param # => 'Foo%20Bar'
230   def to_param
231     URI.escape(self)
232   end
233   
234   # Converts +self+ from an escaped URI parameter value
235   #   'Foo%20Bar'.from_param # => 'Foo Bar'
236   def from_param
237     URI.unescape(self)
238   end
239   
242 class Hash
243   
244   def to_params
245     map { |k,v| "#{k}=#{URI.escape(v)}" }.join('&')
246   end
247   
248   def symbolize_keys
249     self.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
250   end
251   
252   def pass(*keys)
253     reject { |k,v| !keys.include?(k) }
254   end
255   
258 class Symbol
259   
260   def to_proc 
261     Proc.new { |*args| args.shift.__send__(self, *args) }
262   end
263   
266 class Array
267   
268   def to_hash
269     self.inject({}) { |h, (k, v)|  h[k] = v; h }
270   end
271   
272   def to_proc
273     Proc.new { |*args| args.shift.__send__(self[0], *(args + self[1..-1])) }
274   end
275   
278 module Enumerable
279   
280   def eject(&block)
281     find { |e| result = block[e] and break result }
282   end
283   
286 ### Core Extension results for throw :halt
288 class Proc
289   def to_result(cx, *args)
290     cx.instance_eval(&self)
291   end
294 class String
295   def to_result(cx, *args)
296     cx.send :_body=, self
297   end
300 class Array
301   def to_result(cx, *args)
302     self.shift.to_result(cx, *self)
303   end
306 class Symbol
307   def to_result(cx, *args)
308     cx.send(self, *args)
309   end
312 class Fixnum
313   def to_result(cx, *args)
314     cx.status self
315     cx.body args.first
316   end