GitHub Sale: sign up for any paid plan this week and pay nothing until January 1, 2009!  [ hide ]

public
Fork of bmizerany/sinatra
Description: Classy web-development dressed in a DSL
Homepage: http://sinatrarb.com
Clone URL: git://github.com/JackDanger/sinatra.git
Blake Mizerany (author)
Thu Nov 29 18:35:06 -0800 2007
commit  9ee50b308247b18326111b81272944c5c7ce2090
tree    2d0ec39c88f0b7f899b67f3a873a4a3b5c1297d6
parent  bd8515bbf89bcb7502ef5e7099bc6f714ecbf30f
sinatra / lib / sinatra.rb
100644 524 lines (409 sloc) 10.253 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
require 'rubygems'
 
if ENV['SWIFT']
 require 'swiftcore/swiftiplied_mongrel'
 puts "Using Swiftiplied Mongrel"
elsif ENV['EVENT']
  require 'swiftcore/evented_mongrel'
  puts "Using Evented Mongrel"
end
 
require 'rack'
require 'ostruct'
 
class Class
  def dslify_writter(*syms)
    syms.each do |sym|
      class_eval <<-end_eval
def #{sym}(v=nil)
self.send "#{sym}=", v if v
v
end
end_eval
    end
  end
end
 
module Sinatra
  extend self
 
  Result = Struct.new(:block, :params, :status) unless defined?(Result)
  
  def application
    @app ||= Application.new
  end
  
  def application=(app)
    @app = app
  end
  
  def port
    application.options.port
  end
  
  def env
    application.options.env
  end
  
  def run
    
    begin
      puts "== Sinatra has taken the stage on port #{port} for #{env}"
      require 'pp'
      Rack::Handler::Mongrel.run(application, :Port => port) do |server|
        trap(:INT) do
          server.stop
          puts "\n== Sinatra has ended his set (crowd applauds)"
        end
      end
    rescue Errno::EADDRINUSE => e
      puts "== Someone is already performing on port #{port}!"
    end
    
  end
      
  class Event
 
    URI_CHAR = '[^/?:,&#\.]'.freeze unless defined?(URI_CHAR)
    PARAM = /:(#{URI_CHAR}+)/.freeze unless defined?(PARAM)
    
    attr_reader :path, :block, :param_keys, :pattern
    
    def initialize(path, &b)
      @path = path
      @block = b
      @param_keys = []
      regex = @path.to_s.gsub(PARAM) do
        @param_keys << $1.intern
        "(#{URI_CHAR}+)"
      end
      @pattern = /^#{regex}$/
    end
        
    def invoke(env)
      return unless pattern =~ env['PATH_INFO'].squeeze('/')
      params = param_keys.zip($~.captures.map(&:from_param)).to_hash
      Result.new(block, params, 200)
    end
    
  end
  
  class Error
    
    attr_reader :code, :block
    
    def initialize(code, &b)
      @code, @block = code, b
    end
    
    def invoke(env)
      Result.new(block, {}, 404)
    end
    
  end
  
  module ResponseHelpers
 
    def redirect(path)
      throw :halt, Redirect.new(path)
    end
 
  end
  
  module RenderingHelpers
    
    def render(content, options={})
      template = resolve_template(content, options)
      @content = _evaluate_render(template)
      layout = resolve_layout(options[:layout], options)
      @content = _evaluate_render(layout) if layout
      @content
    end
    
    private
      
      def _evaluate_render(content, options={})
        case content
        when String
          instance_eval(%Q{"#{content}"})
        when Proc
          instance_eval(&content)
        when File
          instance_eval(%Q{"#{content.read}"})
        end
      end
      
      def resolve_template(content, options={})
        case content
        when String
          content
        when Symbol
          File.new(filename_for(content, options))
        end
      end
    
      def resolve_layout(name, options={})
        return if name == false
        if layout = layouts[name || :layout]
          return layout
        end
        if File.file?(filename = filename_for(name, options))
          File.new(filename)
        end
      end
      
      def filename_for(name, options={})
        (options[:views_directory] || 'views') + "/#{name}.#{ext}"
      end
              
      def ext
        :html
      end
 
      def layouts
        Sinatra.application.layouts
      end
    
  end
 
  class EventContext
    
    include ResponseHelpers
    include RenderingHelpers
    
    attr_accessor :request, :response
    
    dslify_writter :status, :body
    
    def initialize(request, response, route_params)
      @request = request
      @response = response
      @route_params = route_params
      @response.body = nil
    end
    
    def params
      @params ||= @route_params.merge(@request.params).symbolize_keys
    end
    
    def stop(content)
      throw :halt, content
    end
    
    def complete(returned)
      @response.body || returned
    end
    
    private
 
      def method_missing(name, *args, &b)
        @response.send(name, *args, &b)
      end
    
  end
  
  class Redirect
    def initialize(path)
      @path = path
    end
    
    def to_result(cx, *args)
      cx.status(302)
      cx.header.merge!('Location' => @path)
      cx.body = ''
    end
  end
    
  class Application
    
    attr_reader :events, :layouts, :default_options
    
    def self.default_options
      @@default_options ||= {
        :run => true,
        :port => 4567,
        :env => :development
      }
    end
    
    def default_options
      self.class.default_options
    end
        
    def initialize
      @events = Hash.new { |hash, key| hash[key] = [] }
      @layouts = Hash.new
    end
    
    def define_event(method, path, &b)
      events[method] << event = Event.new(path, &b)
      event
    end
    
    def define_layout(name=:layout, &b)
      layouts[name] = b
    end
    
    def define_error(code, &b)
      events[:errors][code] = Error.new(code, &b)
    end
    
    def lookup(env)
      e = events[env['REQUEST_METHOD'].downcase.to_sym].eject(&[:invoke, env])
      e ||= (events[:errors][404] || basic_not_found).invoke(env)
    end
    
    def basic_not_found
      Error.new(404) do
        '<h1>Not Found</h1>'
      end
    end
    
    def basic_error
      Error.new(500) do
        '<h1>Internal Server Error</h1>'
      end
    end
 
    def options
      @options ||= OpenStruct.new(default_options)
    end
    
    def call(env)
      body = nil
      begin
        result = lookup(env)
        context = EventContext.new(
          Rack::Request.new(env),
          Rack::Response.new,
          result.params
        )
        context.status(result.status)
        returned = catch(:halt) do
          [:complete, context.instance_eval(&result.block)]
        end
        body = returned.to_result(context)
      rescue => e
        env['sinatra.error'] = e
        result = (events[:errors][500] || basic_error).invoke(env)
        returned = catch(:halt) do
          [:complete, context.instance_eval(&result.block)]
        end
        body = returned.to_result(context)
        context.status(500)
      end
      context.body = String === body ? [*body] : body
      context.finish
    end
    
  end
  
end
 
def get(path, &b)
  Sinatra.application.define_event(:get, path, &b)
end
 
def post(path, &b)
  Sinatra.application.define_event(:post, path, &b)
end
 
def put(path, &b)
  Sinatra.application.define_event(:put, path, &b)
end
 
def delete(path, &b)
  Sinatra.application.define_event(:delete, path, &b)
end
 
def helpers(&b)
  Sinatra::EventContext.class_eval(&b)
end
 
def error(code, &b)
  Sinatra.application.define_error(code, &b)
end
 
def layout(name = :layout, &b)
  Sinatra.application.define_layout(name, &b)
end
 
def configures(*envs, &b)
  yield if envs.include?(Sinatra.application.options.env) ||
            envs.empty?
end
 
### Misc Core Extensions
 
module Kernel
 
  def silence_warnings
    old_verbose, $VERBOSE = $VERBOSE, nil
    yield
  ensure
    $VERBOSE = old_verbose
  end
 
end
 
class String
 
  # Converts +self+ to an escaped URI parameter value
  # 'Foo Bar'.to_param # => 'Foo%20Bar'
  def to_param
    URI.escape(self)
  end
  
  # Converts +self+ from an escaped URI parameter value
  # 'Foo%20Bar'.from_param # => 'Foo Bar'
  def from_param
    URI.unescape(self)
  end
  
end
 
class Hash
  
  def to_params
    map { |k,v| "#{k}=#{URI.escape(v)}" }.join('&')
  end
  
  def symbolize_keys
    self.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
  end
  
  def pass(*keys)
    reject { |k,v| !keys.include?(k) }
  end
  
end
 
class Symbol
  
  def to_proc
    Proc.new { |*args| args.shift.__send__(self, *args) }
  end
  
end
 
class Array
  
  def to_hash
    self.inject({}) { |h, (k, v)| h[k] = v; h }
  end
  
  def to_proc
    Proc.new { |*args| args.shift.__send__(self[0], *(args + self[1..-1])) }
  end
  
end
 
module Enumerable
  
  def eject(&block)
    find { |e| result = block[e] and break result }
  end
  
end
 
### Core Extension results for throw :halt
 
class Proc
  def to_result(cx, *args)
    cx.instance_eval(&self)
  end
end
 
class String
  def to_result(cx, *args)
    cx.body = self
  end
end
 
class Array
  def to_result(cx, *args)
    self.shift.to_result(cx, *self)
  end
end
 
class Symbol
  def to_result(cx, *args)
    cx.send(self, *args)
  end
end
 
class Fixnum
  def to_result(cx, *args)
    cx.status self
    cx.body args.first
  end
end
 
class NilClass
  def to_result(cx, *args)
    cx.body = ''
    # log warning here
  end
end
 
at_exit do
  raise $! if $!
  Sinatra.run if Sinatra.application.options.run
end
 
ENV['SINATRA_ENV'] = 'test' if $0 =~ /_test\.rb$/
Sinatra::Application.default_options.merge!(
  :env => (ENV['SINATRA_ENV'] || 'development').to_sym
)
 
configures :development do
  
  get '/sinatra_custom_images/:image.png' do
    File.read(File.dirname(__FILE__) + "/../images/#{params[:image]}.png")
  end
  
  error 404 do
    %Q(
<html>
<body style='text-align: center; color: #888; font-family: Arial; font-size: 22px; margin: 20px'>
<h2>Sinatra doesn't know this diddy.</h2>
<img src='/sinatra_custom_images/404.png'></img>
</body>
</html>
)
  end
  
  error 500 do
    @error = request.env['sinatra.error']
    %Q(
<html>
  <body>
    <style type="text/css" media="screen">
      body {
        font-family: Verdana;
        color: #333;
      }
 
      #content {
        width: 700px;
        margin-left: 20px;
      }
 
      #content h1 {
        width: 99%;
        color: #1D6B8D;
        font-weight: bold;
      }
      
      #stacktrace {
       margin-top: -20px;
      }
 
      #stacktrace pre {
        font-size: 12px;
        border-left: 2px solid #ddd;
        padding-left: 10px;
      }
 
      #stacktrace img {
        margin-top: 10px;
      }
    </style>
    <div id="content">
    <img src="/sinatra_custom_images/500.png" />
      <div id="stacktrace">
        <h1>#{@error.message}</h1>
        <pre><code>#{@error.backtrace.join("\n")}</code></pre>
    </div>
  </body>
</html>
)
  end
  
end