sinatra-bundles 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/Rakefile +2 -1
  2. data/VERSION +1 -1
  3. data/lib/sinatra/bundles/bundle.rb +36 -0
  4. data/lib/sinatra/bundles/helpers.rb +22 -0
  5. data/lib/sinatra/bundles/javascript_bundle.rb +37 -0
  6. data/lib/sinatra/bundles/stylesheet_bundle.rb +36 -0
  7. data/lib/sinatra/bundles.rb +4 -114
  8. data/sinatra-bundles.gemspec +11 -58
  9. data/spec/app.rb +1 -1
  10. data/spec/production_app.rb +1 -1
  11. data/spec/public/javascripts/eval.js +5 -0
  12. data/spec/sinatra-bundles_spec.rb +10 -1
  13. data/spec/spec_helper.rb +5 -6
  14. metadata +17 -57
  15. data/vendor/cache/sinatra-0.10.1.gem +0 -0
  16. data/vendor/gems/sinatra-0.10.1/AUTHORS +0 -43
  17. data/vendor/gems/sinatra-0.10.1/CHANGES +0 -467
  18. data/vendor/gems/sinatra-0.10.1/LICENSE +0 -22
  19. data/vendor/gems/sinatra-0.10.1/README.jp.rdoc +0 -552
  20. data/vendor/gems/sinatra-0.10.1/README.rdoc +0 -622
  21. data/vendor/gems/sinatra-0.10.1/Rakefile +0 -129
  22. data/vendor/gems/sinatra-0.10.1/lib/sinatra/base.rb +0 -1143
  23. data/vendor/gems/sinatra-0.10.1/lib/sinatra/images/404.png +0 -0
  24. data/vendor/gems/sinatra-0.10.1/lib/sinatra/images/500.png +0 -0
  25. data/vendor/gems/sinatra-0.10.1/lib/sinatra/main.rb +0 -28
  26. data/vendor/gems/sinatra-0.10.1/lib/sinatra/showexceptions.rb +0 -303
  27. data/vendor/gems/sinatra-0.10.1/lib/sinatra/tilt.rb +0 -509
  28. data/vendor/gems/sinatra-0.10.1/lib/sinatra.rb +0 -7
  29. data/vendor/gems/sinatra-0.10.1/sinatra.gemspec +0 -86
  30. data/vendor/gems/sinatra-0.10.1/test/base_test.rb +0 -160
  31. data/vendor/gems/sinatra-0.10.1/test/builder_test.rb +0 -65
  32. data/vendor/gems/sinatra-0.10.1/test/contest.rb +0 -64
  33. data/vendor/gems/sinatra-0.10.1/test/erb_test.rb +0 -81
  34. data/vendor/gems/sinatra-0.10.1/test/erubis_test.rb +0 -82
  35. data/vendor/gems/sinatra-0.10.1/test/extensions_test.rb +0 -100
  36. data/vendor/gems/sinatra-0.10.1/test/filter_test.rb +0 -195
  37. data/vendor/gems/sinatra-0.10.1/test/haml_test.rb +0 -90
  38. data/vendor/gems/sinatra-0.10.1/test/helper.rb +0 -76
  39. data/vendor/gems/sinatra-0.10.1/test/helpers_test.rb +0 -573
  40. data/vendor/gems/sinatra-0.10.1/test/mapped_error_test.rb +0 -186
  41. data/vendor/gems/sinatra-0.10.1/test/middleware_test.rb +0 -68
  42. data/vendor/gems/sinatra-0.10.1/test/request_test.rb +0 -33
  43. data/vendor/gems/sinatra-0.10.1/test/response_test.rb +0 -42
  44. data/vendor/gems/sinatra-0.10.1/test/result_test.rb +0 -98
  45. data/vendor/gems/sinatra-0.10.1/test/route_added_hook_test.rb +0 -59
  46. data/vendor/gems/sinatra-0.10.1/test/routing_test.rb +0 -878
  47. data/vendor/gems/sinatra-0.10.1/test/sass_test.rb +0 -79
  48. data/vendor/gems/sinatra-0.10.1/test/server_test.rb +0 -47
  49. data/vendor/gems/sinatra-0.10.1/test/sinatra_test.rb +0 -13
  50. data/vendor/gems/sinatra-0.10.1/test/static_test.rb +0 -87
  51. data/vendor/gems/sinatra-0.10.1/test/templates_test.rb +0 -155
  52. data/vendor/gems/sinatra-0.10.1/test/views/error.builder +0 -3
  53. data/vendor/gems/sinatra-0.10.1/test/views/error.erb +0 -3
  54. data/vendor/gems/sinatra-0.10.1/test/views/error.erubis +0 -3
  55. data/vendor/gems/sinatra-0.10.1/test/views/error.haml +0 -3
  56. data/vendor/gems/sinatra-0.10.1/test/views/error.sass +0 -2
  57. data/vendor/gems/sinatra-0.10.1/test/views/foo/hello.test +0 -1
  58. data/vendor/gems/sinatra-0.10.1/test/views/hello.builder +0 -1
  59. data/vendor/gems/sinatra-0.10.1/test/views/hello.erb +0 -1
  60. data/vendor/gems/sinatra-0.10.1/test/views/hello.erubis +0 -1
  61. data/vendor/gems/sinatra-0.10.1/test/views/hello.haml +0 -1
  62. data/vendor/gems/sinatra-0.10.1/test/views/hello.sass +0 -2
  63. data/vendor/gems/sinatra-0.10.1/test/views/hello.test +0 -1
  64. data/vendor/gems/sinatra-0.10.1/test/views/layout2.builder +0 -3
  65. data/vendor/gems/sinatra-0.10.1/test/views/layout2.erb +0 -2
  66. data/vendor/gems/sinatra-0.10.1/test/views/layout2.erubis +0 -2
  67. data/vendor/gems/sinatra-0.10.1/test/views/layout2.haml +0 -2
  68. data/vendor/gems/sinatra-0.10.1/test/views/layout2.test +0 -1
  69. data/vendor/specifications/sinatra-0.10.1.gemspec +0 -40
@@ -1,1143 +0,0 @@
1
- require 'thread'
2
- require 'time'
3
- require 'uri'
4
- require 'rack'
5
- require 'rack/builder'
6
- require 'sinatra/showexceptions'
7
-
8
- # require tilt if available; fall back on bundled version.
9
- begin
10
- require 'tilt'
11
- rescue LoadError
12
- require 'sinatra/tilt'
13
- end
14
-
15
- module Sinatra
16
- VERSION = '0.10.1'
17
-
18
- # The request object. See Rack::Request for more info:
19
- # http://rack.rubyforge.org/doc/classes/Rack/Request.html
20
- class Request < Rack::Request
21
- def user_agent
22
- @env['HTTP_USER_AGENT']
23
- end
24
-
25
- # Returns an array of acceptable media types for the response
26
- def accept
27
- @env['HTTP_ACCEPT'].to_s.split(',').map { |a| a.strip }
28
- end
29
-
30
- # Override Rack 0.9.x's #params implementation (see #72 in lighthouse)
31
- def params
32
- self.GET.update(self.POST)
33
- rescue EOFError, Errno::ESPIPE
34
- self.GET
35
- end
36
-
37
- def secure?
38
- (@env['HTTP_X_FORWARDED_PROTO'] || @env['rack.url_scheme']) == 'https'
39
- end
40
- end
41
-
42
- # The response object. See Rack::Response and Rack::ResponseHelpers for
43
- # more info:
44
- # http://rack.rubyforge.org/doc/classes/Rack/Response.html
45
- # http://rack.rubyforge.org/doc/classes/Rack/Response/Helpers.html
46
- class Response < Rack::Response
47
- def finish
48
- @body = block if block_given?
49
- if [204, 304].include?(status.to_i)
50
- header.delete "Content-Type"
51
- [status.to_i, header.to_hash, []]
52
- else
53
- body = @body || []
54
- body = [body] if body.respond_to? :to_str
55
- if body.respond_to?(:to_ary)
56
- header["Content-Length"] = body.to_ary.
57
- inject(0) { |len, part| len + Rack::Utils.bytesize(part) }.to_s
58
- end
59
- [status.to_i, header.to_hash, body]
60
- end
61
- end
62
- end
63
-
64
- class NotFound < NameError #:nodoc:
65
- def code ; 404 ; end
66
- end
67
-
68
- # Methods available to routes, before/after filters, and views.
69
- module Helpers
70
- # Set or retrieve the response status code.
71
- def status(value=nil)
72
- response.status = value if value
73
- response.status
74
- end
75
-
76
- # Set or retrieve the response body. When a block is given,
77
- # evaluation is deferred until the body is read with #each.
78
- def body(value=nil, &block)
79
- if block_given?
80
- def block.each ; yield call ; end
81
- response.body = block
82
- else
83
- response.body = value
84
- end
85
- end
86
-
87
- # Halt processing and redirect to the URI provided.
88
- def redirect(uri, *args)
89
- status 302
90
- response['Location'] = uri
91
- halt(*args)
92
- end
93
-
94
- # Halt processing and return the error status provided.
95
- def error(code, body=nil)
96
- code, body = 500, code.to_str if code.respond_to? :to_str
97
- response.body = body unless body.nil?
98
- halt code
99
- end
100
-
101
- # Halt processing and return a 404 Not Found.
102
- def not_found(body=nil)
103
- error 404, body
104
- end
105
-
106
- # Set multiple response headers with Hash.
107
- def headers(hash=nil)
108
- response.headers.merge! hash if hash
109
- response.headers
110
- end
111
-
112
- # Access the underlying Rack session.
113
- def session
114
- env['rack.session'] ||= {}
115
- end
116
-
117
- # Look up a media type by file extension in Rack's mime registry.
118
- def mime_type(type)
119
- Base.mime_type(type)
120
- end
121
-
122
- # Set the Content-Type of the response body given a media type or file
123
- # extension.
124
- def content_type(type, params={})
125
- mime_type = self.mime_type(type)
126
- fail "Unknown media type: %p" % type if mime_type.nil?
127
- if params.any?
128
- params = params.collect { |kv| "%s=%s" % kv }.join(', ')
129
- response['Content-Type'] = [mime_type, params].join(";")
130
- else
131
- response['Content-Type'] = mime_type
132
- end
133
- end
134
-
135
- # Set the Content-Disposition to "attachment" with the specified filename,
136
- # instructing the user agents to prompt to save.
137
- def attachment(filename=nil)
138
- response['Content-Disposition'] = 'attachment'
139
- if filename
140
- params = '; filename="%s"' % File.basename(filename)
141
- response['Content-Disposition'] << params
142
- end
143
- end
144
-
145
- # Use the contents of the file at +path+ as the response body.
146
- def send_file(path, opts={})
147
- stat = File.stat(path)
148
- last_modified stat.mtime
149
-
150
- content_type mime_type(opts[:type]) ||
151
- mime_type(File.extname(path)) ||
152
- response['Content-Type'] ||
153
- 'application/octet-stream'
154
-
155
- response['Content-Length'] ||= (opts[:length] || stat.size).to_s
156
-
157
- if opts[:disposition] == 'attachment' || opts[:filename]
158
- attachment opts[:filename] || path
159
- elsif opts[:disposition] == 'inline'
160
- response['Content-Disposition'] = 'inline'
161
- end
162
-
163
- halt StaticFile.open(path, 'rb')
164
- rescue Errno::ENOENT
165
- not_found
166
- end
167
-
168
- # Rack response body used to deliver static files. The file contents are
169
- # generated iteratively in 8K chunks.
170
- class StaticFile < ::File #:nodoc:
171
- alias_method :to_path, :path
172
- def each
173
- rewind
174
- while buf = read(8192)
175
- yield buf
176
- end
177
- end
178
- end
179
-
180
- # Specify response freshness policy for HTTP caches (Cache-Control header).
181
- # Any number of non-value directives (:public, :private, :no_cache,
182
- # :no_store, :must_revalidate, :proxy_revalidate) may be passed along with
183
- # a Hash of value directives (:max_age, :min_stale, :s_max_age).
184
- #
185
- # cache_control :public, :must_revalidate, :max_age => 60
186
- # => Cache-Control: public, must-revalidate, max-age=60
187
- #
188
- # See RFC 2616 / 14.9 for more on standard cache control directives:
189
- # http://tools.ietf.org/html/rfc2616#section-14.9.1
190
- def cache_control(*values)
191
- if values.last.kind_of?(Hash)
192
- hash = values.pop
193
- hash.reject! { |k,v| v == false }
194
- hash.reject! { |k,v| values << k if v == true }
195
- else
196
- hash = {}
197
- end
198
-
199
- values = values.map { |value| value.to_s.tr('_','-') }
200
- hash.each { |k,v| values << [k.to_s.tr('_', '-'), v].join('=') }
201
-
202
- response['Cache-Control'] = values.join(', ') if values.any?
203
- end
204
-
205
- # Set the Expires header and Cache-Control/max-age directive. Amount
206
- # can be an integer number of seconds in the future or a Time object
207
- # indicating when the response should be considered "stale". The remaining
208
- # "values" arguments are passed to the #cache_control helper:
209
- #
210
- # expires 500, :public, :must_revalidate
211
- # => Cache-Control: public, must-revalidate, max-age=60
212
- # => Expires: Mon, 08 Jun 2009 08:50:17 GMT
213
- #
214
- def expires(amount, *values)
215
- values << {} unless values.last.kind_of?(Hash)
216
-
217
- if amount.respond_to?(:to_time)
218
- max_age = amount.to_time - Time.now
219
- time = amount.to_time
220
- else
221
- max_age = amount
222
- time = Time.now + amount
223
- end
224
-
225
- values.last.merge!(:max_age => max_age)
226
- cache_control(*values)
227
-
228
- response['Expires'] = time.httpdate
229
- end
230
-
231
- # Set the last modified time of the resource (HTTP 'Last-Modified' header)
232
- # and halt if conditional GET matches. The +time+ argument is a Time,
233
- # DateTime, or other object that responds to +to_time+.
234
- #
235
- # When the current request includes an 'If-Modified-Since' header that
236
- # matches the time specified, execution is immediately halted with a
237
- # '304 Not Modified' response.
238
- def last_modified(time)
239
- time = time.to_time if time.respond_to?(:to_time)
240
- time = time.httpdate if time.respond_to?(:httpdate)
241
- response['Last-Modified'] = time
242
- halt 304 if time == request.env['HTTP_IF_MODIFIED_SINCE']
243
- time
244
- end
245
-
246
- # Set the response entity tag (HTTP 'ETag' header) and halt if conditional
247
- # GET matches. The +value+ argument is an identifier that uniquely
248
- # identifies the current version of the resource. The +kind+ argument
249
- # indicates whether the etag should be used as a :strong (default) or :weak
250
- # cache validator.
251
- #
252
- # When the current request includes an 'If-None-Match' header with a
253
- # matching etag, execution is immediately halted. If the request method is
254
- # GET or HEAD, a '304 Not Modified' response is sent.
255
- def etag(value, kind=:strong)
256
- raise TypeError, ":strong or :weak expected" if ![:strong,:weak].include?(kind)
257
- value = '"%s"' % value
258
- value = 'W/' + value if kind == :weak
259
- response['ETag'] = value
260
-
261
- # Conditional GET check
262
- if etags = env['HTTP_IF_NONE_MATCH']
263
- etags = etags.split(/\s*,\s*/)
264
- halt 304 if etags.include?(value) || etags.include?('*')
265
- end
266
- end
267
-
268
- ## Sugar for redirect (example: redirect back)
269
- def back ; request.referer ; end
270
-
271
- end
272
-
273
- # Template rendering methods. Each method takes the name of a template
274
- # to render as a Symbol and returns a String with the rendered output,
275
- # as well as an optional hash with additional options.
276
- #
277
- # `template` is either the name or path of the template as symbol
278
- # (Use `:'subdir/myview'` for views in subdirectories), or a string
279
- # that will be rendered.
280
- #
281
- # Possible options are:
282
- # :layout If set to false, no layout is rendered, otherwise
283
- # the specified layout is used (Ignored for `sass`)
284
- # :locals A hash with local variables that should be available
285
- # in the template
286
- module Templates
287
- def erb(template, options={}, locals={})
288
- render :erb, template, options, locals
289
- end
290
-
291
- def erubis(template, options={}, locals={})
292
- render :erubis, template, options, locals
293
- end
294
-
295
- def haml(template, options={}, locals={})
296
- render :haml, template, options, locals
297
- end
298
-
299
- def sass(template, options={}, locals={})
300
- options[:layout] = false
301
- render :sass, template, options, locals
302
- end
303
-
304
- def builder(template=nil, options={}, locals={}, &block)
305
- options, template = template, nil if template.is_a?(Hash)
306
- template = Proc.new { block } if template.nil?
307
- render :builder, template, options, locals
308
- end
309
-
310
- private
311
- def render(engine, data, options={}, locals={}, &block)
312
- # merge app-level options
313
- options = settings.send(engine).merge(options) if settings.respond_to?(engine)
314
-
315
- # extract generic options
316
- locals = options.delete(:locals) || locals || {}
317
- views = options.delete(:views) || settings.views || "./views"
318
- layout = options.delete(:layout)
319
- layout = :layout if layout.nil? || layout == true
320
-
321
- # compile and render template
322
- template = compile_template(engine, data, options, views)
323
- output = template.render(self, locals, &block)
324
-
325
- # render layout
326
- if layout
327
- begin
328
- options = options.merge(:views => views, :layout => false)
329
- output = render(engine, layout, options, locals) { output }
330
- rescue Errno::ENOENT
331
- end
332
- end
333
-
334
- output
335
- end
336
-
337
- def compile_template(engine, data, options, views)
338
- @template_cache.fetch engine, data, options do
339
- case
340
- when data.is_a?(Symbol)
341
- body, path, line = self.class.templates[data]
342
- if body
343
- body = body.call if body.respond_to?(:call)
344
- Tilt[engine].new(path, line.to_i, options) { body }
345
- else
346
- path = ::File.join(views, "#{data}.#{engine}")
347
- Tilt[engine].new(path, 1, options)
348
- end
349
- when data.is_a?(Proc) || data.is_a?(String)
350
- body = data.is_a?(String) ? Proc.new { data } : data
351
- path, line = self.class.caller_locations.first
352
- Tilt[engine].new(path, line.to_i, options, &body)
353
- else
354
- raise ArgumentError
355
- end
356
- end
357
- end
358
- end
359
-
360
- # Base class for all Sinatra applications and middleware.
361
- class Base
362
- include Rack::Utils
363
- include Helpers
364
- include Templates
365
-
366
- attr_accessor :app
367
-
368
- def initialize(app=nil)
369
- @app = app
370
- @template_cache = Tilt::Cache.new
371
- yield self if block_given?
372
- end
373
-
374
- # Rack call interface.
375
- def call(env)
376
- dup.call!(env)
377
- end
378
-
379
- attr_accessor :env, :request, :response, :params
380
-
381
- def call!(env)
382
- @env = env
383
- @request = Request.new(env)
384
- @response = Response.new
385
- @params = indifferent_params(@request.params)
386
-
387
- invoke { dispatch! }
388
- invoke { error_block!(response.status) }
389
-
390
- status, header, body = @response.finish
391
-
392
- # Never produce a body on HEAD requests. Do retain the Content-Length
393
- # unless it's "0", in which case we assume it was calculated erroneously
394
- # for a manual HEAD response and remove it entirely.
395
- if @env['REQUEST_METHOD'] == 'HEAD'
396
- body = []
397
- header.delete('Content-Length') if header['Content-Length'] == '0'
398
- end
399
-
400
- [status, header, body]
401
- end
402
-
403
- # Access settings defined with Base.set.
404
- def settings
405
- self.class
406
- end
407
- alias_method :options, :settings
408
-
409
- # Exit the current block, halts any further processing
410
- # of the request, and returns the specified response.
411
- def halt(*response)
412
- response = response.first if response.length == 1
413
- throw :halt, response
414
- end
415
-
416
- # Pass control to the next matching route.
417
- # If there are no more matching routes, Sinatra will
418
- # return a 404 response.
419
- def pass(&block)
420
- throw :pass, block
421
- end
422
-
423
- # Forward the request to the downstream app -- middleware only.
424
- def forward
425
- fail "downstream app not set" unless @app.respond_to? :call
426
- status, headers, body = @app.call(@request.env)
427
- @response.status = status
428
- @response.body = body
429
- @response.headers.merge! headers
430
- nil
431
- end
432
-
433
- private
434
- # Run before filters defined on the class and all superclasses.
435
- def before_filter!(base=self.class)
436
- before_filter!(base.superclass) if base.superclass.respond_to?(:before_filters)
437
- base.before_filters.each { |block| instance_eval(&block) }
438
- end
439
-
440
- # Run after filters defined on the class and all superclasses.
441
- def after_filter!(base=self.class)
442
- after_filter!(base.superclass) if base.superclass.respond_to?(:after_filters)
443
- base.after_filters.each { |block| instance_eval(&block) }
444
- end
445
-
446
- # Run routes defined on the class and all superclasses.
447
- def route!(base=self.class, pass_block=nil)
448
- if routes = base.routes[@request.request_method]
449
- original_params = @params
450
- path = unescape(@request.path_info)
451
-
452
- routes.each do |pattern, keys, conditions, block|
453
- if match = pattern.match(path)
454
- values = match.captures.to_a
455
- params =
456
- if keys.any?
457
- keys.zip(values).inject({}) do |hash,(k,v)|
458
- if k == 'splat'
459
- (hash[k] ||= []) << v
460
- else
461
- hash[k] = v
462
- end
463
- hash
464
- end
465
- elsif values.any?
466
- {'captures' => values}
467
- else
468
- {}
469
- end
470
- @params = original_params.merge(params)
471
- @block_params = values
472
-
473
- pass_block = catch(:pass) do
474
- conditions.each { |cond|
475
- throw :pass if instance_eval(&cond) == false }
476
- route_eval(&block)
477
- end
478
- end
479
- end
480
-
481
- @params = original_params
482
- end
483
-
484
- # Run routes defined in superclass.
485
- if base.superclass.respond_to?(:routes)
486
- route! base.superclass, pass_block
487
- return
488
- end
489
-
490
- route_eval(&pass_block) if pass_block
491
-
492
- route_missing
493
- end
494
-
495
- # Run a route block and throw :halt with the result.
496
- def route_eval(&block)
497
- throw :halt, instance_eval(&block)
498
- end
499
-
500
- # No matching route was found or all routes passed. The default
501
- # implementation is to forward the request downstream when running
502
- # as middleware (@app is non-nil); when no downstream app is set, raise
503
- # a NotFound exception. Subclasses can override this method to perform
504
- # custom route miss logic.
505
- def route_missing
506
- if @app
507
- forward
508
- else
509
- raise NotFound
510
- end
511
- end
512
-
513
- # Attempt to serve static files from public directory. Throws :halt when
514
- # a matching file is found, returns nil otherwise.
515
- def static!
516
- return if (public_dir = settings.public).nil?
517
- public_dir = File.expand_path(public_dir)
518
-
519
- path = File.expand_path(public_dir + unescape(request.path_info))
520
- return if path[0, public_dir.length] != public_dir
521
- return unless File.file?(path)
522
-
523
- send_file path, :disposition => nil
524
- end
525
-
526
- # Enable string or symbol key access to the nested params hash.
527
- def indifferent_params(params)
528
- params = indifferent_hash.merge(params)
529
- params.each do |key, value|
530
- next unless value.is_a?(Hash)
531
- params[key] = indifferent_params(value)
532
- end
533
- end
534
-
535
- def indifferent_hash
536
- Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
537
- end
538
-
539
- # Run the block with 'throw :halt' support and apply result to the response.
540
- def invoke(&block)
541
- res = catch(:halt) { instance_eval(&block) }
542
- return if res.nil?
543
-
544
- case
545
- when res.respond_to?(:to_str)
546
- @response.body = [res]
547
- when res.respond_to?(:to_ary)
548
- res = res.to_ary
549
- if Fixnum === res.first
550
- if res.length == 3
551
- @response.status, headers, body = res
552
- @response.body = body if body
553
- headers.each { |k, v| @response.headers[k] = v } if headers
554
- elsif res.length == 2
555
- @response.status = res.first
556
- @response.body = res.last
557
- else
558
- raise TypeError, "#{res.inspect} not supported"
559
- end
560
- else
561
- @response.body = res
562
- end
563
- when res.respond_to?(:each)
564
- @response.body = res
565
- when (100...599) === res
566
- @response.status = res
567
- end
568
-
569
- res
570
- end
571
-
572
- # Dispatch a request with error handling.
573
- def dispatch!
574
- static! if settings.static? && (request.get? || request.head?)
575
- before_filter!
576
- route!
577
- rescue NotFound => boom
578
- handle_not_found!(boom)
579
- rescue ::Exception => boom
580
- handle_exception!(boom)
581
- ensure
582
- after_filter!
583
- end
584
-
585
- def handle_not_found!(boom)
586
- @env['sinatra.error'] = boom
587
- @response.status = 404
588
- @response.headers['X-Cascade'] = 'pass'
589
- @response.body = ['<h1>Not Found</h1>']
590
- error_block! boom.class, NotFound
591
- end
592
-
593
- def handle_exception!(boom)
594
- @env['sinatra.error'] = boom
595
-
596
- dump_errors!(boom) if settings.dump_errors?
597
- raise boom if settings.raise_errors? || settings.show_exceptions?
598
-
599
- @response.status = 500
600
- error_block! boom.class, Exception
601
- end
602
-
603
- # Find an custom error block for the key(s) specified.
604
- def error_block!(*keys)
605
- keys.each do |key|
606
- base = self.class
607
- while base.respond_to?(:errors)
608
- if block = base.errors[key]
609
- # found a handler, eval and return result
610
- res = instance_eval(&block)
611
- return res
612
- else
613
- base = base.superclass
614
- end
615
- end
616
- end
617
- nil
618
- end
619
-
620
- def dump_errors!(boom)
621
- backtrace = clean_backtrace(boom.backtrace)
622
- msg = ["#{boom.class} - #{boom.message}:",
623
- *backtrace].join("\n ")
624
- @env['rack.errors'].puts(msg)
625
- end
626
-
627
- def clean_backtrace(trace)
628
- return trace unless settings.clean_trace?
629
-
630
- trace.reject { |line|
631
- line =~ /lib\/sinatra.*\.rb/ ||
632
- (defined?(Gem) && line.include?(Gem.dir))
633
- }.map! { |line| line.gsub(/^\.\//, '') }
634
- end
635
-
636
- class << self
637
- attr_reader :routes, :before_filters, :after_filters, :templates, :errors
638
-
639
- def reset!
640
- @conditions = []
641
- @routes = {}
642
- @before_filters = []
643
- @after_filters = []
644
- @errors = {}
645
- @middleware = []
646
- @prototype = nil
647
- @extensions = []
648
-
649
- if superclass.respond_to?(:templates)
650
- @templates = Hash.new { |hash,key| superclass.templates[key] }
651
- else
652
- @templates = {}
653
- end
654
- end
655
-
656
- # Extension modules registered on this class and all superclasses.
657
- def extensions
658
- if superclass.respond_to?(:extensions)
659
- (@extensions + superclass.extensions).uniq
660
- else
661
- @extensions
662
- end
663
- end
664
-
665
- # Middleware used in this class and all superclasses.
666
- def middleware
667
- if superclass.respond_to?(:middleware)
668
- superclass.middleware + @middleware
669
- else
670
- @middleware
671
- end
672
- end
673
-
674
- # Sets an option to the given value. If the value is a proc,
675
- # the proc will be called every time the option is accessed.
676
- def set(option, value=self)
677
- if value.kind_of?(Proc)
678
- metadef(option, &value)
679
- metadef("#{option}?") { !!__send__(option) }
680
- metadef("#{option}=") { |val| set(option, Proc.new{val}) }
681
- elsif value == self && option.respond_to?(:to_hash)
682
- option.to_hash.each { |k,v| set(k, v) }
683
- elsif respond_to?("#{option}=")
684
- __send__ "#{option}=", value
685
- else
686
- set option, Proc.new{value}
687
- end
688
- self
689
- end
690
-
691
- # Same as calling `set :option, true` for each of the given options.
692
- def enable(*opts)
693
- opts.each { |key| set(key, true) }
694
- end
695
-
696
- # Same as calling `set :option, false` for each of the given options.
697
- def disable(*opts)
698
- opts.each { |key| set(key, false) }
699
- end
700
-
701
- # Define a custom error handler. Optionally takes either an Exception
702
- # class, or an HTTP status code to specify which errors should be
703
- # handled.
704
- def error(codes=Exception, &block)
705
- Array(codes).each { |code| @errors[code] = block }
706
- end
707
-
708
- # Sugar for `error(404) { ... }`
709
- def not_found(&block)
710
- error 404, &block
711
- end
712
-
713
- # Define a named template. The block must return the template source.
714
- def template(name, &block)
715
- filename, line = caller_locations.first
716
- templates[name] = [block, filename, line.to_i]
717
- end
718
-
719
- # Define the layout template. The block must return the template source.
720
- def layout(name=:layout, &block)
721
- template name, &block
722
- end
723
-
724
- # Load embeded templates from the file; uses the caller's __FILE__
725
- # when no file is specified.
726
- def inline_templates=(file=nil)
727
- file = (file.nil? || file == true) ? caller_files.first : file
728
-
729
- begin
730
- app, data =
731
- ::IO.read(file).gsub("\r\n", "\n").split(/^__END__$/, 2)
732
- rescue Errno::ENOENT
733
- app, data = nil
734
- end
735
-
736
- if data
737
- lines = app.count("\n") + 1
738
- template = nil
739
- data.each_line do |line|
740
- lines += 1
741
- if line =~ /^@@\s*(.*)/
742
- template = ''
743
- templates[$1.to_sym] = [template, file, lines]
744
- elsif template
745
- template << line
746
- end
747
- end
748
- end
749
- end
750
-
751
- # Lookup or register a mime type in Rack's mime registry.
752
- def mime_type(type, value=nil)
753
- return type if type.nil? || type.to_s.include?('/')
754
- type = ".#{type}" unless type.to_s[0] == ?.
755
- return Rack::Mime.mime_type(type, nil) unless value
756
- Rack::Mime::MIME_TYPES[type] = value
757
- end
758
-
759
- # Define a before filter; runs before all requests within the same
760
- # context as route handlers and may access/modify the request and
761
- # response.
762
- def before(&block)
763
- @before_filters << block
764
- end
765
-
766
- # Define an after filter; runs after all requests within the same
767
- # context as route handlers and may access/modify the request and
768
- # response.
769
- def after(&block)
770
- @after_filters << block
771
- end
772
-
773
- # Add a route condition. The route is considered non-matching when the
774
- # block returns false.
775
- def condition(&block)
776
- @conditions << block
777
- end
778
-
779
- private
780
- def host_name(pattern)
781
- condition { pattern === request.host }
782
- end
783
-
784
- def user_agent(pattern)
785
- condition {
786
- if request.user_agent =~ pattern
787
- @params[:agent] = $~[1..-1]
788
- true
789
- else
790
- false
791
- end
792
- }
793
- end
794
- alias_method :agent, :user_agent
795
-
796
- def provides(*types)
797
- types = [types] unless types.kind_of? Array
798
- types.map!{|t| mime_type(t)}
799
-
800
- condition {
801
- matching_types = (request.accept & types)
802
- unless matching_types.empty?
803
- response.headers['Content-Type'] = matching_types.first
804
- true
805
- else
806
- false
807
- end
808
- }
809
- end
810
-
811
- public
812
- # Defining a `GET` handler also automatically defines
813
- # a `HEAD` handler.
814
- def get(path, opts={}, &block)
815
- conditions = @conditions.dup
816
- route('GET', path, opts, &block)
817
-
818
- @conditions = conditions
819
- route('HEAD', path, opts, &block)
820
- end
821
-
822
- def put(path, opts={}, &bk); route 'PUT', path, opts, &bk end
823
- def post(path, opts={}, &bk); route 'POST', path, opts, &bk end
824
- def delete(path, opts={}, &bk); route 'DELETE', path, opts, &bk end
825
- def head(path, opts={}, &bk); route 'HEAD', path, opts, &bk end
826
-
827
- private
828
- def route(verb, path, options={}, &block)
829
- # Because of self.options.host
830
- host_name(options.delete(:host)) if options.key?(:host)
831
-
832
- options.each {|option, args| send(option, *args)}
833
-
834
- pattern, keys = compile(path)
835
- conditions, @conditions = @conditions, []
836
-
837
- define_method "#{verb} #{path}", &block
838
- unbound_method = instance_method("#{verb} #{path}")
839
- block =
840
- if block.arity != 0
841
- lambda { unbound_method.bind(self).call(*@block_params) }
842
- else
843
- lambda { unbound_method.bind(self).call }
844
- end
845
-
846
- invoke_hook(:route_added, verb, path, block)
847
-
848
- (@routes[verb] ||= []).
849
- push([pattern, keys, conditions, block]).last
850
- end
851
-
852
- def invoke_hook(name, *args)
853
- extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
854
- end
855
-
856
- def compile(path)
857
- keys = []
858
- if path.respond_to? :to_str
859
- special_chars = %w{. + ( )}
860
- pattern =
861
- path.to_str.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
862
- case match
863
- when "*"
864
- keys << 'splat'
865
- "(.*?)"
866
- when *special_chars
867
- Regexp.escape(match)
868
- else
869
- keys << $2[1..-1]
870
- "([^/?&#]+)"
871
- end
872
- end
873
- [/^#{pattern}$/, keys]
874
- elsif path.respond_to?(:keys) && path.respond_to?(:match)
875
- [path, path.keys]
876
- elsif path.respond_to? :match
877
- [path, keys]
878
- else
879
- raise TypeError, path
880
- end
881
- end
882
-
883
- public
884
- # Makes the methods defined in the block and in the Modules given
885
- # in `extensions` available to the handlers and templates
886
- def helpers(*extensions, &block)
887
- class_eval(&block) if block_given?
888
- include(*extensions) if extensions.any?
889
- end
890
-
891
- def register(*extensions, &block)
892
- extensions << Module.new(&block) if block_given?
893
- @extensions += extensions
894
- extensions.each do |extension|
895
- extend extension
896
- extension.registered(self) if extension.respond_to?(:registered)
897
- end
898
- end
899
-
900
- def development?; environment == :development end
901
- def production?; environment == :production end
902
- def test?; environment == :test end
903
-
904
- # Set configuration options for Sinatra and/or the app.
905
- # Allows scoping of settings for certain environments.
906
- def configure(*envs, &block)
907
- yield self if envs.empty? || envs.include?(environment.to_sym)
908
- end
909
-
910
- # Use the specified Rack middleware
911
- def use(middleware, *args, &block)
912
- @prototype = nil
913
- @middleware << [middleware, args, block]
914
- end
915
-
916
- # Run the Sinatra app as a self-hosted server using
917
- # Thin, Mongrel or WEBrick (in that order)
918
- def run!(options={})
919
- set options
920
- handler = detect_rack_handler
921
- handler_name = handler.name.gsub(/.*::/, '')
922
- puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
923
- "on #{port} for #{environment} with backup from #{handler_name}" unless handler_name =~/cgi/i
924
- handler.run self, :Host => host, :Port => port do |server|
925
- trap(:INT) do
926
- ## Use thins' hard #stop! if available, otherwise just #stop
927
- server.respond_to?(:stop!) ? server.stop! : server.stop
928
- puts "\n== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i
929
- end
930
- set :running, true
931
- end
932
- rescue Errno::EADDRINUSE => e
933
- puts "== Someone is already performing on port #{port}!"
934
- end
935
-
936
- # The prototype instance used to process requests.
937
- def prototype
938
- @prototype ||= new
939
- end
940
-
941
- # Create a new instance of the class fronted by its middleware
942
- # pipeline. The object is guaranteed to respond to #call but may not be
943
- # an instance of the class new was called on.
944
- def new(*args, &bk)
945
- builder = Rack::Builder.new
946
- builder.use Rack::Session::Cookie if sessions?
947
- builder.use Rack::CommonLogger if logging?
948
- builder.use Rack::MethodOverride if methodoverride?
949
- builder.use ShowExceptions if show_exceptions?
950
- middleware.each { |c,a,b| builder.use(c, *a, &b) }
951
-
952
- builder.run super
953
- builder.to_app
954
- end
955
-
956
- def call(env)
957
- synchronize { prototype.call(env) }
958
- end
959
-
960
- private
961
- def detect_rack_handler
962
- servers = Array(self.server)
963
- servers.each do |server_name|
964
- begin
965
- return Rack::Handler.get(server_name.downcase)
966
- rescue LoadError
967
- rescue NameError
968
- end
969
- end
970
- fail "Server handler (#{servers.join(',')}) not found."
971
- end
972
-
973
- def inherited(subclass)
974
- subclass.reset!
975
- super
976
- end
977
-
978
- @@mutex = Mutex.new
979
- def synchronize(&block)
980
- if lock?
981
- @@mutex.synchronize(&block)
982
- else
983
- yield
984
- end
985
- end
986
-
987
- def metadef(message, &block)
988
- (class << self; self; end).
989
- send :define_method, message, &block
990
- end
991
-
992
- public
993
- CALLERS_TO_IGNORE = [
994
- /\/sinatra(\/(base|main|showexceptions))?\.rb$/, # all sinatra code
995
- /lib\/tilt.*\.rb$/, # all tilt code
996
- /\(.*\)/, # generated code
997
- /custom_require\.rb$/, # rubygems require hacks
998
- /active_support/, # active_support require hacks
999
- ]
1000
-
1001
- # add rubinius (and hopefully other VM impls) ignore patterns ...
1002
- CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS) if defined?(RUBY_IGNORE_CALLERS)
1003
-
1004
- # Like Kernel#caller but excluding certain magic entries and without
1005
- # line / method information; the resulting array contains filenames only.
1006
- def caller_files
1007
- caller_locations.
1008
- map { |file,line| file }
1009
- end
1010
-
1011
- def caller_locations
1012
- caller(1).
1013
- map { |line| line.split(/:(?=\d|in )/)[0,2] }.
1014
- reject { |file,line| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
1015
- end
1016
- end
1017
-
1018
- reset!
1019
-
1020
- set :raise_errors, true
1021
- set :dump_errors, false
1022
- set :clean_trace, true
1023
- set :show_exceptions, false
1024
- set :sessions, false
1025
- set :logging, false
1026
- set :methodoverride, false
1027
- set :static, false
1028
- set :environment, (ENV['RACK_ENV'] || :development).to_sym
1029
-
1030
- set :run, false # start server via at-exit hook?
1031
- set :running, false # is the built-in server running now?
1032
- set :server, %w[thin mongrel webrick]
1033
- set :host, '0.0.0.0'
1034
- set :port, 4567
1035
-
1036
- set :app_file, nil
1037
- set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
1038
- set :views, Proc.new { root && File.join(root, 'views') }
1039
- set :public, Proc.new { root && File.join(root, 'public') }
1040
- set :lock, false
1041
-
1042
- error ::Exception do
1043
- response.status = 500
1044
- content_type 'text/html'
1045
- '<h1>Internal Server Error</h1>'
1046
- end
1047
-
1048
- configure :development do
1049
- get '/__sinatra__/:image.png' do
1050
- filename = File.dirname(__FILE__) + "/images/#{params[:image]}.png"
1051
- content_type :png
1052
- send_file filename
1053
- end
1054
-
1055
- error NotFound do
1056
- content_type 'text/html'
1057
-
1058
- (<<-HTML).gsub(/^ {8}/, '')
1059
- <!DOCTYPE html>
1060
- <html>
1061
- <head>
1062
- <style type="text/css">
1063
- body { text-align:center;font-family:helvetica,arial;font-size:22px;
1064
- color:#888;margin:20px}
1065
- #c {margin:0 auto;width:500px;text-align:left}
1066
- </style>
1067
- </head>
1068
- <body>
1069
- <h2>Sinatra doesn't know this ditty.</h2>
1070
- <img src='/__sinatra__/404.png'>
1071
- <div id="c">
1072
- Try this:
1073
- <pre>#{request.request_method.downcase} '#{request.path_info}' do\n "Hello World"\nend</pre>
1074
- </div>
1075
- </body>
1076
- </html>
1077
- HTML
1078
- end
1079
- end
1080
- end
1081
-
1082
- # Base class for classic style (top-level) applications.
1083
- class Default < Base
1084
- set :raise_errors, Proc.new { test? }
1085
- set :show_exceptions, Proc.new { development? }
1086
- set :dump_errors, true
1087
- set :sessions, false
1088
- set :logging, Proc.new { ! test? }
1089
- set :methodoverride, true
1090
- set :static, true
1091
- set :run, Proc.new { ! test? }
1092
-
1093
- def self.register(*extensions, &block) #:nodoc:
1094
- added_methods = extensions.map {|m| m.public_instance_methods }.flatten
1095
- Delegator.delegate(*added_methods)
1096
- super(*extensions, &block)
1097
- end
1098
- end
1099
-
1100
- # The top-level Application. All DSL methods executed on main are delegated
1101
- # to this class.
1102
- class Application < Default
1103
- end
1104
-
1105
- # Sinatra delegation mixin. Mixing this module into an object causes all
1106
- # methods to be delegated to the Sinatra::Application class. Used primarily
1107
- # at the top-level.
1108
- module Delegator #:nodoc:
1109
- def self.delegate(*methods)
1110
- methods.each do |method_name|
1111
- eval <<-RUBY, binding, '(__DELEGATE__)', 1
1112
- def #{method_name}(*args, &b)
1113
- ::Sinatra::Application.send(#{method_name.inspect}, *args, &b)
1114
- end
1115
- private #{method_name.inspect}
1116
- RUBY
1117
- end
1118
- end
1119
-
1120
- delegate :get, :put, :post, :delete, :head, :template, :layout,
1121
- :before, :after, :error, :not_found, :configure, :set, :mime_type,
1122
- :enable, :disable, :use, :development?, :test?,
1123
- :production?, :use_in_file_templates!, :helpers
1124
- end
1125
-
1126
- # Create a new Sinatra application. The block is evaluated in the new app's
1127
- # class scope.
1128
- def self.new(base=Base, options={}, &block)
1129
- base = Class.new(base)
1130
- base.send :class_eval, &block if block_given?
1131
- base
1132
- end
1133
-
1134
- # Extend the top-level DSL with the modules provided.
1135
- def self.register(*extensions, &block)
1136
- Default.register(*extensions, &block)
1137
- end
1138
-
1139
- # Include the helper modules provided in Sinatra's request context.
1140
- def self.helpers(*extensions, &block)
1141
- Default.helpers(*extensions, &block)
1142
- end
1143
- end