e9172c6468f295e9a32784e7a54fafdf28929cc2
[sinatra.git] / lib / sinatra.rb
blobe9172c6468f295e9a32784e7a54fafdf28929cc2
1 require 'rubygems'
3 if ENV['SWIFT']
4  require 'swiftcore/swiftiplied_mongrel'
5  puts "Using Swiftiplied Mongrel"
6 elsif ENV['EVENT']
7   require 'swiftcore/evented_mongrel' 
8   puts "Using Evented Mongrel"
9 end
11 require 'rack'
12 require 'ostruct'
14 class Class
15   def dslify_writter(*syms)
16     syms.each do |sym|
17       class_eval <<-end_eval
18         def #{sym}(v=nil)
19           self.send "#{sym}=", v if v
20           v
21         end
22       end_eval
23     end
24   end
25 end
27 module Sinatra
28   extend self
30   Result = Struct.new(:block, :params)
31   
32   def application
33     @app ||= Application.new
34   end
35   
36   def application=(app)
37     @app = app
38   end
39   
40   def port
41     application.options.port
42   end
43   
44   def env
45     application.options.env
46   end
47   
48   def run
49     
50     begin
51       puts "== Sinatra has taken the stage on port #{port} for #{env}"
52       require 'pp'
53       Rack::Handler::Mongrel.run(application, :Port => port) do |server|
54         trap(:INT) do
55           server.stop
56           puts "\n== Sinatra has ended his set (crowd applauds)"
57         end
58       end
59     rescue Errno::EADDRINUSE => e
60       puts "== Someone is already performing on port #{port}!"
61     end
62     
63   end
64       
65   class Event
67     URI_CHAR = '[^/?:,&#]'.freeze unless defined?(URI_CHAR)
68     PARAM = /:(#{URI_CHAR}+)/.freeze unless defined?(PARAM)
69     
70     attr_reader :path, :block, :param_keys, :pattern
71     
72     def initialize(path, &b)
73       @path = path
74       @block = b
75       @param_keys = []
76       regex = @path.to_s.gsub(PARAM) do
77         @param_keys << $1.intern
78         "(#{URI_CHAR}+)"
79       end
80       @pattern = /^#{regex}$/
81     end
82         
83     def invoke(env)
84       return unless pattern =~ env['PATH_INFO'].squeeze('/')
85       params = param_keys.zip($~.captures.map(&:from_param)).to_hash
86       Result.new(block, params)
87     end
88     
89   end
90   
91   class EventContext
92     
93     module ResponseHelpers
95       def redirect(path)
96         throw :halt, Redirect.new(path)
97       end
99     end
100     include ResponseHelpers
101     
102     module RenderingHelpers
103       
104       def render(content, options={})
105         template = resolve_template(content, options)
106         @content = _evaluate_render(template)
107         layout = resolve_layout(options[:layout], options)
108         @content = _evaluate_render(layout) if layout
109         @content
110       end
111       
112       private
113         
114         def _evaluate_render(content, options={})
115           case content
116           when String
117             instance_eval(%Q{"#{content}"})
118           when Proc
119             instance_eval(&content)
120           when File
121             instance_eval(%Q{"#{content.read}"})
122           end
123         end
124         
125         def resolve_template(content, options={})
126           case content
127           when String
128             content
129           when Symbol
130             File.new(filename_for(content, options))
131           end
132         end
133       
134         def resolve_layout(name, options={})
135           return if name == false
136           if layout = layouts[name || :layout]
137             return layout
138           end
139           if File.file?(filename = filename_for(name, options))
140             File.new(filename)
141           end
142         end
143         
144         def filename_for(name, options={})
145           (options[:views_directory] || 'views') + "/#{name}.#{ext}"
146         end
147                 
148         def ext
149           :html
150         end
152         def layouts
153           Sinatra.application.layouts
154         end
155       
156     end
157     include RenderingHelpers
158     
159     attr_accessor :request, :response
160     
161     dslify_writter :status, :body
162     
163     def initialize(request, response, route_params)
164       @request = request
165       @response = response
166       @route_params = route_params
167       @response.body = nil
168     end
169     
170     def params
171       @params ||= @route_params.merge(@request.params).symbolize_keys
172     end
173     
174     def stop(content)
175       throw :halt, content
176     end
177     
178     def complete(returned)
179       @response.body || returned
180     end
181     
182     private
184       def method_missing(name, *args, &b)
185         @response.send(name, *args, &b)
186       end
187     
188   end
189   
190   class Redirect
191     def initialize(path)
192       @path = path
193     end
194     
195     def to_result(cx, *args)
196       cx.status(302)
197       cx.header.merge!('Location' => @path)
198       cx.body = ''
199     end
200   end
201     
202   class Application
203     
204     attr_reader :events, :layouts, :default_options
205     
206     def self.default_options
207       @@default_options = {
208         :run => true,
209         :port => 4567,
210         :environment => :development
211       }
212     end
213     
214     def default_options
215       @@default_options
216     end
217     
218     def initialize
219       @events = Hash.new { |hash, key| hash[key] = [] }
220       @layouts = Hash.new
221     end
222     
223     def define_event(method, path, &b)
224       events[method] << event = Event.new(path, &b)
225       event
226     end
227     
228     def define_layout(name=:layout, &b)
229       layouts[name] = b
230     end
231     
232     def lookup(env)
233       events[env['REQUEST_METHOD'].downcase.to_sym].eject(&[:invoke, env])
234     end
236     def options
237       @options ||= OpenStruct.new(default_options)
238     end
239     
240     def call(env)
241       return [404, {}, 'Not Found'] unless result = lookup(env)
242       context = EventContext.new(
243         Rack::Request.new(env), 
244         Rack::Response.new,
245         result.params
246       )
247       returned = catch(:halt) do
248         [:complete, context.instance_eval(&result.block)]
249       end
250       result = returned.to_result(context)
251       context.body = String === result ? [*result] : result
252       context.finish
253     end
254         
255   end
256   
259 def get(path, &b)
260   Sinatra.application.define_event(:get, path, &b)
263 def post(path, &b)
264   Sinatra.application.define_event(:post, path, &b)
267 def put(path, &b)
268   Sinatra.application.define_event(:put, path, &b)
271 def delete(path, &b)
272   Sinatra.application.define_event(:delete, path, &b)
275 def helpers(&b)
276   Sinatra::EventContext.class_eval(&b)
279 def layout(name = :layout, &b)
280   Sinatra.application.define_layout(name, &b)
283 ### Misc Core Extensions
285 module Kernel
287   def silence_warnings
288     old_verbose, $VERBOSE = $VERBOSE, nil
289     yield
290   ensure
291     $VERBOSE = old_verbose
292   end
296 class String
298   # Converts +self+ to an escaped URI parameter value
299   #   'Foo Bar'.to_param # => 'Foo%20Bar'
300   def to_param
301     URI.escape(self)
302   end
303   
304   # Converts +self+ from an escaped URI parameter value
305   #   'Foo%20Bar'.from_param # => 'Foo Bar'
306   def from_param
307     URI.unescape(self)
308   end
309   
312 class Hash
313   
314   def to_params
315     map { |k,v| "#{k}=#{URI.escape(v)}" }.join('&')
316   end
317   
318   def symbolize_keys
319     self.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
320   end
321   
322   def pass(*keys)
323     reject { |k,v| !keys.include?(k) }
324   end
325   
328 class Symbol
329   
330   def to_proc 
331     Proc.new { |*args| args.shift.__send__(self, *args) }
332   end
333   
336 class Array
337   
338   def to_hash
339     self.inject({}) { |h, (k, v)|  h[k] = v; h }
340   end
341   
342   def to_proc
343     Proc.new { |*args| args.shift.__send__(self[0], *(args + self[1..-1])) }
344   end
345   
348 module Enumerable
349   
350   def eject(&block)
351     find { |e| result = block[e] and break result }
352   end
353   
356 ### Core Extension results for throw :halt
358 class Proc
359   def to_result(cx, *args)
360     cx.instance_eval(&self)
361   end
364 class String
365   def to_result(cx, *args)
366     cx.body = self
367   end
370 class Array
371   def to_result(cx, *args)
372     self.shift.to_result(cx, *self)
373   end
376 class Symbol
377   def to_result(cx, *args)
378     cx.send(self, *args)
379   end
382 class Fixnum
383   def to_result(cx, *args)
384     cx.status self
385     cx.body args.first
386   end
389 class NilClass
390   def to_result(cx, *args)
391     cx.body = ''
392     # log warning here
393   end
396 at_exit do
397   Sinatra.run if Sinatra.application.options.run