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