sinatra-bundles 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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