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