darkhelmet-sinatra 0.9.1.1 → 0.10.1

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 (88) hide show
  1. data/AUTHORS +2 -0
  2. data/CHANGES +180 -0
  3. data/LICENSE +1 -1
  4. data/README.jp.rdoc +552 -0
  5. data/README.rdoc +177 -38
  6. data/Rakefile +18 -25
  7. data/lib/sinatra.rb +1 -2
  8. data/lib/sinatra/base.rb +405 -305
  9. data/lib/sinatra/main.rb +5 -24
  10. data/lib/sinatra/showexceptions.rb +303 -0
  11. data/lib/sinatra/tilt.rb +509 -0
  12. data/sinatra.gemspec +21 -51
  13. data/test/base_test.rb +123 -93
  14. data/test/builder_test.rb +2 -1
  15. data/test/contest.rb +64 -0
  16. data/test/erb_test.rb +1 -1
  17. data/test/erubis_test.rb +82 -0
  18. data/test/extensions_test.rb +24 -8
  19. data/test/filter_test.rb +99 -3
  20. data/test/haml_test.rb +25 -3
  21. data/test/helper.rb +43 -48
  22. data/test/helpers_test.rb +500 -424
  23. data/test/mapped_error_test.rb +163 -137
  24. data/test/middleware_test.rb +3 -3
  25. data/test/request_test.rb +16 -1
  26. data/test/response_test.rb +2 -2
  27. data/test/result_test.rb +1 -1
  28. data/test/route_added_hook_test.rb +59 -0
  29. data/test/routing_test.rb +170 -22
  30. data/test/sass_test.rb +44 -1
  31. data/test/server_test.rb +19 -13
  32. data/test/sinatra_test.rb +1 -1
  33. data/test/static_test.rb +9 -2
  34. data/test/templates_test.rb +78 -11
  35. data/test/views/error.builder +3 -0
  36. data/test/views/error.erb +3 -0
  37. data/test/views/error.erubis +3 -0
  38. data/test/views/error.haml +3 -0
  39. data/test/views/error.sass +2 -0
  40. data/test/views/foo/hello.test +1 -0
  41. data/test/views/hello.erubis +1 -0
  42. data/test/views/layout2.erubis +2 -0
  43. metadata +37 -55
  44. data/compat/app_test.rb +0 -282
  45. data/compat/application_test.rb +0 -262
  46. data/compat/builder_test.rb +0 -101
  47. data/compat/compat_test.rb +0 -12
  48. data/compat/custom_error_test.rb +0 -62
  49. data/compat/erb_test.rb +0 -136
  50. data/compat/events_test.rb +0 -78
  51. data/compat/filter_test.rb +0 -30
  52. data/compat/haml_test.rb +0 -233
  53. data/compat/helper.rb +0 -30
  54. data/compat/mapped_error_test.rb +0 -72
  55. data/compat/pipeline_test.rb +0 -45
  56. data/compat/public/foo.xml +0 -1
  57. data/compat/sass_test.rb +0 -57
  58. data/compat/sessions_test.rb +0 -42
  59. data/compat/streaming_test.rb +0 -133
  60. data/compat/sym_params_test.rb +0 -19
  61. data/compat/template_test.rb +0 -30
  62. data/compat/use_in_file_templates_test.rb +0 -47
  63. data/compat/views/foo.builder +0 -1
  64. data/compat/views/foo.erb +0 -1
  65. data/compat/views/foo.haml +0 -1
  66. data/compat/views/foo.sass +0 -2
  67. data/compat/views/foo_layout.erb +0 -2
  68. data/compat/views/foo_layout.haml +0 -2
  69. data/compat/views/layout_test/foo.builder +0 -1
  70. data/compat/views/layout_test/foo.erb +0 -1
  71. data/compat/views/layout_test/foo.haml +0 -1
  72. data/compat/views/layout_test/foo.sass +0 -2
  73. data/compat/views/layout_test/layout.builder +0 -3
  74. data/compat/views/layout_test/layout.erb +0 -1
  75. data/compat/views/layout_test/layout.haml +0 -1
  76. data/compat/views/layout_test/layout.sass +0 -2
  77. data/compat/views/no_layout/no_layout.builder +0 -1
  78. data/compat/views/no_layout/no_layout.haml +0 -1
  79. data/lib/sinatra/compat.rb +0 -250
  80. data/lib/sinatra/test.rb +0 -126
  81. data/lib/sinatra/test/bacon.rb +0 -19
  82. data/lib/sinatra/test/rspec.rb +0 -13
  83. data/lib/sinatra/test/spec.rb +0 -11
  84. data/lib/sinatra/test/unit.rb +0 -13
  85. data/test/data/reload_app_file.rb +0 -3
  86. data/test/options_test.rb +0 -374
  87. data/test/reload_test.rb +0 -68
  88. data/test/test_test.rb +0 -144
@@ -3,6 +3,5 @@ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
3
3
 
4
4
  require 'sinatra/base'
5
5
  require 'sinatra/main'
6
- require 'sinatra/compat'
7
6
 
8
- use_in_file_templates!
7
+ enable :inline_templates
@@ -3,10 +3,17 @@ require 'time'
3
3
  require 'uri'
4
4
  require 'rack'
5
5
  require 'rack/builder'
6
- require 'colored'
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
7
14
 
8
15
  module Sinatra
9
- VERSION = '0.9.1.1'
16
+ VERSION = '0.10.1'
10
17
 
11
18
  # The request object. See Rack::Request for more info:
12
19
  # http://rack.rubyforge.org/doc/classes/Rack/Request.html
@@ -15,6 +22,7 @@ module Sinatra
15
22
  @env['HTTP_USER_AGENT']
16
23
  end
17
24
 
25
+ # Returns an array of acceptable media types for the response
18
26
  def accept
19
27
  @env['HTTP_ACCEPT'].to_s.split(',').map { |a| a.strip }
20
28
  end
@@ -22,9 +30,13 @@ module Sinatra
22
30
  # Override Rack 0.9.x's #params implementation (see #72 in lighthouse)
23
31
  def params
24
32
  self.GET.update(self.POST)
25
- rescue EOFError => boom
33
+ rescue EOFError, Errno::ESPIPE
26
34
  self.GET
27
35
  end
36
+
37
+ def secure?
38
+ (@env['HTTP_X_FORWARDED_PROTO'] || @env['rack.url_scheme']) == 'https'
39
+ end
28
40
  end
29
41
 
30
42
  # The response object. See Rack::Response and Rack::ResponseHelpers for
@@ -32,16 +44,6 @@ module Sinatra
32
44
  # http://rack.rubyforge.org/doc/classes/Rack/Response.html
33
45
  # http://rack.rubyforge.org/doc/classes/Rack/Response/Helpers.html
34
46
  class Response < Rack::Response
35
- def initialize
36
- @status, @body = 200, []
37
- @header = Rack::Utils::HeaderHash.new({'Content-Type' => 'text/html'})
38
- end
39
-
40
- def write(str)
41
- @body << str.to_s
42
- str
43
- end
44
-
45
47
  def finish
46
48
  @body = block if block_given?
47
49
  if [204, 304].include?(status.to_i)
@@ -52,7 +54,7 @@ module Sinatra
52
54
  body = [body] if body.respond_to? :to_str
53
55
  if body.respond_to?(:to_ary)
54
56
  header["Content-Length"] = body.to_ary.
55
- inject(0) { |len, part| len + part.bytesize }.to_s
57
+ inject(0) { |len, part| len + Rack::Utils.bytesize(part) }.to_s
56
58
  end
57
59
  [status.to_i, header.to_hash, body]
58
60
  end
@@ -63,7 +65,7 @@ module Sinatra
63
65
  def code ; 404 ; end
64
66
  end
65
67
 
66
- # Methods available to routes, before filters, and views.
68
+ # Methods available to routes, before/after filters, and views.
67
69
  module Helpers
68
70
  # Set or retrieve the response status code.
69
71
  def status(value=nil)
@@ -113,20 +115,20 @@ module Sinatra
113
115
  end
114
116
 
115
117
  # Look up a media type by file extension in Rack's mime registry.
116
- def media_type(type)
117
- Base.media_type(type)
118
+ def mime_type(type)
119
+ Base.mime_type(type)
118
120
  end
119
121
 
120
122
  # Set the Content-Type of the response body given a media type or file
121
123
  # extension.
122
124
  def content_type(type, params={})
123
- media_type = self.media_type(type)
124
- fail "Unknown media type: %p" % type if media_type.nil?
125
+ mime_type = self.mime_type(type)
126
+ fail "Unknown media type: %p" % type if mime_type.nil?
125
127
  if params.any?
126
128
  params = params.collect { |kv| "%s=%s" % kv }.join(', ')
127
- response['Content-Type'] = [media_type, params].join(";")
129
+ response['Content-Type'] = [mime_type, params].join(";")
128
130
  else
129
- response['Content-Type'] = media_type
131
+ response['Content-Type'] = mime_type
130
132
  end
131
133
  end
132
134
 
@@ -145,8 +147,8 @@ module Sinatra
145
147
  stat = File.stat(path)
146
148
  last_modified stat.mtime
147
149
 
148
- content_type media_type(opts[:type]) ||
149
- media_type(File.extname(path)) ||
150
+ content_type mime_type(opts[:type]) ||
151
+ mime_type(File.extname(path)) ||
150
152
  response['Content-Type'] ||
151
153
  'application/octet-stream'
152
154
 
@@ -163,6 +165,8 @@ module Sinatra
163
165
  not_found
164
166
  end
165
167
 
168
+ # Rack response body used to deliver static files. The file contents are
169
+ # generated iteratively in 8K chunks.
166
170
  class StaticFile < ::File #:nodoc:
167
171
  alias_method :to_path, :path
168
172
  def each
@@ -173,6 +177,57 @@ module Sinatra
173
177
  end
174
178
  end
175
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
+
176
231
  # Set the last modified time of the resource (HTTP 'Last-Modified' header)
177
232
  # and halt if conditional GET matches. The +time+ argument is a Time,
178
233
  # DateTime, or other object that responds to +to_time+.
@@ -190,7 +245,7 @@ module Sinatra
190
245
 
191
246
  # Set the response entity tag (HTTP 'ETag' header) and halt if conditional
192
247
  # GET matches. The +value+ argument is an identifier that uniquely
193
- # identifies the current version of the resource. The +strength+ argument
248
+ # identifies the current version of the resource. The +kind+ argument
194
249
  # indicates whether the etag should be used as a :strong (default) or :weak
195
250
  # cache validator.
196
251
  #
@@ -215,110 +270,90 @@ module Sinatra
215
270
 
216
271
  end
217
272
 
218
- # Template rendering methods. Each method takes a the name of a template
219
- # to render as a Symbol and returns a String with the rendered output.
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
220
286
  module Templates
221
- def erb(template, options={})
222
- require 'erb' unless defined? ::ERB
223
- render :erb, template, options
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
224
293
  end
225
294
 
226
- def haml(template, options={})
227
- require 'haml' unless defined? ::Haml
228
- options[:options] ||= self.class.haml if self.class.respond_to? :haml
229
- render :haml, template, options
295
+ def haml(template, options={}, locals={})
296
+ render :haml, template, options, locals
230
297
  end
231
298
 
232
- def sass(template, options={}, &block)
233
- require 'sass' unless defined? ::Sass
299
+ def sass(template, options={}, locals={})
234
300
  options[:layout] = false
235
- render :sass, template, options
301
+ render :sass, template, options, locals
236
302
  end
237
303
 
238
- def builder(template=nil, options={}, &block)
239
- require 'builder' unless defined? ::Builder
304
+ def builder(template=nil, options={}, locals={}, &block)
240
305
  options, template = template, nil if template.is_a?(Hash)
241
- template = lambda { block } if template.nil?
242
- render :builder, template, options
306
+ template = Proc.new { block } if template.nil?
307
+ render :builder, template, options, locals
243
308
  end
244
309
 
245
310
  private
246
- def render(engine, template, options={}) #:nodoc:
247
- data = lookup_template(engine, template, options)
248
- output = __send__("render_#{engine}", template, data, options)
249
- layout, data = lookup_layout(engine, options)
250
- if layout
251
- __send__("render_#{engine}", layout, data, options) { output }
252
- else
253
- output
254
- end
255
- end
256
-
257
- def lookup_template(engine, template, options={})
258
- case template
259
- when Symbol
260
- if cached = self.class.templates[template]
261
- lookup_template(engine, cached, options)
262
- else
263
- ::File.read(template_path(engine, template, options))
264
- end
265
- when Proc
266
- template.call
267
- when String
268
- template
269
- else
270
- raise ArgumentError
271
- end
272
- end
273
-
274
- def lookup_layout(engine, options)
275
- return if options[:layout] == false
276
- options.delete(:layout) if options[:layout] == true
277
- template = options[:layout] || :layout
278
- data = lookup_template(engine, template, options)
279
- [template, data]
280
- rescue Errno::ENOENT
281
- nil
282
- end
283
-
284
- def template_path(engine, template, options={})
285
- views_dir =
286
- options[:views_directory] || self.options.views || "./views"
287
- "#{views_dir}/#{template}.#{engine}"
288
- end
289
-
290
- def render_erb(template, data, options, &block)
291
- original_out_buf = @_out_buf
292
- data = data.call if data.kind_of? Proc
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)
293
314
 
294
- instance = ::ERB.new(data, nil, nil, '@_out_buf')
295
- locals = options[:locals] || {}
296
- locals_assigns = locals.to_a.collect { |k,v| "#{k} = locals[:#{k}]" }
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
297
320
 
298
- src = "#{locals_assigns.join("\n")}\n#{instance.src}"
299
- eval src, binding, '(__ERB__)', locals_assigns.length + 1
300
- @_out_buf, result = original_out_buf, @_out_buf
301
- result
302
- end
321
+ # compile and render template
322
+ template = compile_template(engine, data, options, views)
323
+ output = template.render(self, locals, &block)
303
324
 
304
- def render_haml(template, data, options, &block)
305
- engine = ::Haml::Engine.new(data, options[:options] || {})
306
- engine.render(self, options[:locals] || {}, &block)
307
- end
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
308
333
 
309
- def render_sass(template, data, options, &block)
310
- engine = ::Sass::Engine.new(data, options[:sass] || {})
311
- engine.render
334
+ output
312
335
  end
313
336
 
314
- def render_builder(template, data, options, &block)
315
- xml = ::Builder::XmlMarkup.new(:indent => 2)
316
- if data.respond_to?(:to_str)
317
- eval data.to_str, binding, '<BUILDER>', 1
318
- elsif data.kind_of?(Proc)
319
- data.call(xml)
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
320
356
  end
321
- xml.target!
322
357
  end
323
358
  end
324
359
 
@@ -332,6 +367,7 @@ module Sinatra
332
367
 
333
368
  def initialize(app=nil)
334
369
  @app = app
370
+ @template_cache = Tilt::Cache.new
335
371
  yield self if block_given?
336
372
  end
337
373
 
@@ -346,7 +382,7 @@ module Sinatra
346
382
  @env = env
347
383
  @request = Request.new(env)
348
384
  @response = Response.new
349
- @params = nil
385
+ @params = indifferent_params(@request.params)
350
386
 
351
387
  invoke { dispatch! }
352
388
  invoke { error_block!(response.status) }
@@ -364,20 +400,24 @@ module Sinatra
364
400
  [status, header, body]
365
401
  end
366
402
 
367
- # Access options defined with Base.set.
368
- def options
403
+ # Access settings defined with Base.set.
404
+ def settings
369
405
  self.class
370
406
  end
407
+ alias_method :options, :settings
371
408
 
372
- # Exit the current block and halt the response.
409
+ # Exit the current block, halts any further processing
410
+ # of the request, and returns the specified response.
373
411
  def halt(*response)
374
412
  response = response.first if response.length == 1
375
413
  throw :halt, response
376
414
  end
377
415
 
378
416
  # Pass control to the next matching route.
379
- def pass
380
- throw :pass
417
+ # If there are no more matching routes, Sinatra will
418
+ # return a 404 response.
419
+ def pass(&block)
420
+ throw :pass, block
381
421
  end
382
422
 
383
423
  # Forward the request to the downstream app -- middleware only.
@@ -391,21 +431,26 @@ module Sinatra
391
431
  end
392
432
 
393
433
  private
394
- # Run before filters and then locate and run a matching route.
395
- def route!
396
- @params = nested_params(@request.params)
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
397
439
 
398
- # before filters
399
- self.class.filters.each { |block| instance_eval(&block) }
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
400
445
 
401
- # routes
402
- if routes = self.class.routes[@request.request_method]
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]
403
449
  original_params = @params
404
450
  path = unescape(@request.path_info)
405
451
 
406
- routes.each do |pattern, rpath, keys, conditions, block|
452
+ routes.each do |pattern, keys, conditions, block|
407
453
  if match = pattern.match(path)
408
- @env['rack.errors'].write("=== Path '#{path}' matched route '#{rpath}'\n".green)
409
454
  values = match.captures.to_a
410
455
  params =
411
456
  if keys.any?
@@ -425,19 +470,39 @@ module Sinatra
425
470
  @params = original_params.merge(params)
426
471
  @block_params = values
427
472
 
428
- catch(:pass) do
473
+ pass_block = catch(:pass) do
429
474
  conditions.each { |cond|
430
475
  throw :pass if instance_eval(&cond) == false }
431
- throw :halt, instance_eval(&block)
476
+ route_eval(&block)
432
477
  end
433
- else
434
- @env['rack.errors'].write("=== Path '#{path}' failed route '#{rpath}'\n".red)
435
478
  end
436
479
  end
480
+
481
+ @params = original_params
437
482
  end
438
483
 
439
- # No matching route found or all routes passed -- forward downstream
440
- # when running as middleware; 404 when running as normal app.
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
441
506
  if @app
442
507
  forward
443
508
  else
@@ -445,17 +510,25 @@ module Sinatra
445
510
  end
446
511
  end
447
512
 
448
- def nested_params(params)
449
- return indifferent_hash.merge(params) if !params.keys.join.include?('[')
450
- params.inject indifferent_hash do |res, (key,val)|
451
- if key.include?('[')
452
- head = key.split(/[\]\[]+/)
453
- last = head.pop
454
- head.inject(res){ |hash,k| hash[k] ||= indifferent_hash }[last] = val
455
- else
456
- res[key] = val
457
- end
458
- res
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)
459
532
  end
460
533
  end
461
534
 
@@ -498,25 +571,30 @@ module Sinatra
498
571
 
499
572
  # Dispatch a request with error handling.
500
573
  def dispatch!
574
+ static! if settings.static? && (request.get? || request.head?)
575
+ before_filter!
501
576
  route!
502
577
  rescue NotFound => boom
503
578
  handle_not_found!(boom)
504
579
  rescue ::Exception => boom
505
580
  handle_exception!(boom)
581
+ ensure
582
+ after_filter!
506
583
  end
507
584
 
508
585
  def handle_not_found!(boom)
509
- @env['sinatra.error'] = boom
510
- @response.status = 404
511
- @response.body = ['<h1>Not Found</h1>']
586
+ @env['sinatra.error'] = boom
587
+ @response.status = 404
588
+ @response.headers['X-Cascade'] = 'pass'
589
+ @response.body = ['<h1>Not Found</h1>']
512
590
  error_block! boom.class, NotFound
513
591
  end
514
592
 
515
593
  def handle_exception!(boom)
516
594
  @env['sinatra.error'] = boom
517
595
 
518
- dump_errors!(boom) if options.dump_errors?
519
- raise boom if options.raise_errors?
596
+ dump_errors!(boom) if settings.dump_errors?
597
+ raise boom if settings.raise_errors? || settings.show_exceptions?
520
598
 
521
599
  @response.status = 500
522
600
  error_block! boom.class, Exception
@@ -524,11 +602,16 @@ module Sinatra
524
602
 
525
603
  # Find an custom error block for the key(s) specified.
526
604
  def error_block!(*keys)
527
- errmap = self.class.errors
528
605
  keys.each do |key|
529
- if block = errmap[key]
530
- res = instance_eval(&block)
531
- return res
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
532
615
  end
533
616
  end
534
617
  nil
@@ -538,11 +621,11 @@ module Sinatra
538
621
  backtrace = clean_backtrace(boom.backtrace)
539
622
  msg = ["#{boom.class} - #{boom.message}:",
540
623
  *backtrace].join("\n ")
541
- @env['rack.errors'].write(msg)
624
+ @env['rack.errors'].puts(msg)
542
625
  end
543
626
 
544
627
  def clean_backtrace(trace)
545
- return trace unless options.clean_trace?
628
+ return trace unless settings.clean_trace?
546
629
 
547
630
  trace.reject { |line|
548
631
  line =~ /lib\/sinatra.*\.rb/ ||
@@ -550,20 +633,46 @@ module Sinatra
550
633
  }.map! { |line| line.gsub(/^\.\//, '') }
551
634
  end
552
635
 
553
- @routes = {}
554
- @filters = []
555
- @conditions = []
556
- @templates = {}
557
- @middleware = []
558
- @errors = {}
559
- @prototype = nil
560
- @extensions = []
561
-
562
636
  class << self
563
- attr_accessor :routes, :filters, :conditions, :templates,
564
- :middleware, :errors
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
565
655
 
566
- public
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.
567
676
  def set(option, value=self)
568
677
  if value.kind_of?(Proc)
569
678
  metadef(option, &value)
@@ -579,45 +688,59 @@ module Sinatra
579
688
  self
580
689
  end
581
690
 
691
+ # Same as calling `set :option, true` for each of the given options.
582
692
  def enable(*opts)
583
693
  opts.each { |key| set(key, true) }
584
694
  end
585
695
 
696
+ # Same as calling `set :option, false` for each of the given options.
586
697
  def disable(*opts)
587
698
  opts.each { |key| set(key, false) }
588
699
  end
589
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.
590
704
  def error(codes=Exception, &block)
591
- if codes.respond_to? :each
592
- codes.each { |err| error(err, &block) }
593
- else
594
- @errors[codes] = block
595
- end
705
+ Array(codes).each { |code| @errors[code] = block }
596
706
  end
597
707
 
708
+ # Sugar for `error(404) { ... }`
598
709
  def not_found(&block)
599
710
  error 404, &block
600
711
  end
601
712
 
713
+ # Define a named template. The block must return the template source.
602
714
  def template(name, &block)
603
- templates[name] = block
715
+ filename, line = caller_locations.first
716
+ templates[name] = [block, filename, line.to_i]
604
717
  end
605
718
 
719
+ # Define the layout template. The block must return the template source.
606
720
  def layout(name=:layout, &block)
607
721
  template name, &block
608
722
  end
609
723
 
610
- def use_in_file_templates!
611
- ignore = [/lib\/sinatra.*\.rb/, /\(.*\)/, /rubygems\/custom_require\.rb/]
612
- file = caller.
613
- map { |line| line.sub(/:\d+.*$/, '') }.
614
- find { |line| ignore.all? { |pattern| line !~ pattern } }
615
- if data = ::IO.read(file).split('__END__')[1]
616
- data.gsub!(/\r\n/, "\n")
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
617
738
  template = nil
618
739
  data.each_line do |line|
740
+ lines += 1
619
741
  if line =~ /^@@\s*(.*)/
620
- template = templates[$1.to_sym] = ''
742
+ template = ''
743
+ templates[$1.to_sym] = [template, file, lines]
621
744
  elsif template
622
745
  template << line
623
746
  end
@@ -625,17 +748,30 @@ module Sinatra
625
748
  end
626
749
  end
627
750
 
628
- # Look up a media type by file extension in Rack's mime registry.
629
- def media_type(type)
751
+ # Lookup or register a mime type in Rack's mime registry.
752
+ def mime_type(type, value=nil)
630
753
  return type if type.nil? || type.to_s.include?('/')
631
754
  type = ".#{type}" unless type.to_s[0] == ?.
632
- Rack::Mime.mime_type(type, nil)
755
+ return Rack::Mime.mime_type(type, nil) unless value
756
+ Rack::Mime::MIME_TYPES[type] = value
633
757
  end
634
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.
635
762
  def before(&block)
636
- @filters << 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
637
771
  end
638
772
 
773
+ # Add a route condition. The route is considered non-matching when the
774
+ # block returns false.
639
775
  def condition(&block)
640
776
  @conditions << block
641
777
  end
@@ -655,10 +791,11 @@ module Sinatra
655
791
  end
656
792
  }
657
793
  end
794
+ alias_method :agent, :user_agent
658
795
 
659
- def accept_mime_types(types)
796
+ def provides(*types)
660
797
  types = [types] unless types.kind_of? Array
661
- types.map!{|t| media_type(t)}
798
+ types.map!{|t| mime_type(t)}
662
799
 
663
800
  condition {
664
801
  matching_types = (request.accept & types)
@@ -672,6 +809,8 @@ module Sinatra
672
809
  end
673
810
 
674
811
  public
812
+ # Defining a `GET` handler also automatically defines
813
+ # a `HEAD` handler.
675
814
  def get(path, opts={}, &block)
676
815
  conditions = @conditions.dup
677
816
  route('GET', path, opts, &block)
@@ -680,16 +819,17 @@ module Sinatra
680
819
  route('HEAD', path, opts, &block)
681
820
  end
682
821
 
683
- def put(path, opts={}, &bk); route 'PUT', path, opts, &bk; end
684
- def post(path, opts={}, &bk); route 'POST', path, opts, &bk; end
685
- def delete(path, opts={}, &bk); route 'DELETE', path, opts, &bk; end
686
- def head(path, opts={}, &bk); route 'HEAD', path, opts, &bk; end
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
687
826
 
688
827
  private
689
- def route(verb, path, opts={}, &block)
690
- host_name opts[:host] if opts.key?(:host)
691
- user_agent opts[:agent] if opts.key?(:agent)
692
- accept_mime_types opts[:provides] if opts.key?(:provides)
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)}
693
833
 
694
834
  pattern, keys = compile(path)
695
835
  conditions, @conditions = @conditions, []
@@ -703,10 +843,10 @@ module Sinatra
703
843
  lambda { unbound_method.bind(self).call }
704
844
  end
705
845
 
706
- invoke_hook(:route_added, verb, path)
846
+ invoke_hook(:route_added, verb, path, block)
707
847
 
708
- (routes[verb] ||= []).
709
- push([pattern, path, keys, conditions, block]).last
848
+ (@routes[verb] ||= []).
849
+ push([pattern, keys, conditions, block]).last
710
850
  end
711
851
 
712
852
  def invoke_hook(name, *args)
@@ -718,7 +858,7 @@ module Sinatra
718
858
  if path.respond_to? :to_str
719
859
  special_chars = %w{. + ( )}
720
860
  pattern =
721
- path.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
861
+ path.to_str.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
722
862
  case match
723
863
  when "*"
724
864
  keys << 'splat'
@@ -731,6 +871,8 @@ module Sinatra
731
871
  end
732
872
  end
733
873
  [/^#{pattern}$/, keys]
874
+ elsif path.respond_to?(:keys) && path.respond_to?(:match)
875
+ [path, path.keys]
734
876
  elsif path.respond_to? :match
735
877
  [path, keys]
736
878
  else
@@ -739,13 +881,11 @@ module Sinatra
739
881
  end
740
882
 
741
883
  public
884
+ # Makes the methods defined in the block and in the Modules given
885
+ # in `extensions` available to the handlers and templates
742
886
  def helpers(*extensions, &block)
743
887
  class_eval(&block) if block_given?
744
- include *extensions if extensions.any?
745
- end
746
-
747
- def extensions
748
- (@extensions + (superclass.extensions rescue [])).uniq
888
+ include(*extensions) if extensions.any?
749
889
  end
750
890
 
751
891
  def register(*extensions, &block)
@@ -757,20 +897,24 @@ module Sinatra
757
897
  end
758
898
  end
759
899
 
760
- def development? ; environment == :development ; end
761
- def test? ; environment == :test ; end
762
- def production? ; environment == :production ; end
900
+ def development?; environment == :development end
901
+ def production?; environment == :production end
902
+ def test?; environment == :test end
763
903
 
904
+ # Set configuration options for Sinatra and/or the app.
905
+ # Allows scoping of settings for certain environments.
764
906
  def configure(*envs, &block)
765
- return if reloading?
766
- yield if envs.empty? || envs.include?(environment.to_sym)
907
+ yield self if envs.empty? || envs.include?(environment.to_sym)
767
908
  end
768
909
 
910
+ # Use the specified Rack middleware
769
911
  def use(middleware, *args, &block)
770
912
  @prototype = nil
771
913
  @middleware << [middleware, args, block]
772
914
  end
773
915
 
916
+ # Run the Sinatra app as a self-hosted server using
917
+ # Thin, Mongrel or WEBrick (in that order)
774
918
  def run!(options={})
775
919
  set options
776
920
  handler = detect_rack_handler
@@ -783,6 +927,7 @@ module Sinatra
783
927
  server.respond_to?(:stop!) ? server.stop! : server.stop
784
928
  puts "\n== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i
785
929
  end
930
+ set :running, true
786
931
  end
787
932
  rescue Errno::EADDRINUSE => e
788
933
  puts "== Someone is already performing on port #{port}!"
@@ -798,50 +943,18 @@ module Sinatra
798
943
  # an instance of the class new was called on.
799
944
  def new(*args, &bk)
800
945
  builder = Rack::Builder.new
801
- builder.use Rack::Session::Cookie if sessions? && !test?
802
- builder.use Rack::CommonLogger if logging?
803
- builder.use Rack::MethodOverride if methodoverride?
804
- @middleware.each { |c, args, bk| builder.use(c, *args, &bk) }
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
+
805
952
  builder.run super
806
953
  builder.to_app
807
954
  end
808
955
 
809
956
  def call(env)
810
- synchronize do
811
- reload! if reload?
812
- prototype.call(env)
813
- end
814
- end
815
-
816
- def reloading?
817
- @reloading
818
- end
819
-
820
- def reload!
821
- @reloading = true
822
- reset!
823
- $LOADED_FEATURES.delete("sinatra.rb")
824
- ::Kernel.load app_file
825
- @reloading = false
826
- end
827
-
828
- def reset!(base=superclass)
829
- @routes = base.dupe_routes
830
- @templates = base.templates.dup
831
- @conditions = []
832
- @filters = base.filters.dup
833
- @errors = base.errors.dup
834
- @middleware = base.middleware.dup
835
- @prototype = nil
836
- @extensions = []
837
- end
838
-
839
- protected
840
- def dupe_routes
841
- routes.inject({}) do |hash,(request_method,routes)|
842
- hash[request_method] = routes.dup
843
- hash
844
- end
957
+ synchronize { prototype.call(env) }
845
958
  end
846
959
 
847
960
  private
@@ -849,7 +962,7 @@ module Sinatra
849
962
  servers = Array(self.server)
850
963
  servers.each do |server_name|
851
964
  begin
852
- return Rack::Handler.get(server_name)
965
+ return Rack::Handler.get(server_name.downcase)
853
966
  rescue LoadError
854
967
  rescue NameError
855
968
  end
@@ -858,7 +971,7 @@ module Sinatra
858
971
  end
859
972
 
860
973
  def inherited(subclass)
861
- subclass.reset! self
974
+ subclass.reset!
862
975
  super
863
976
  end
864
977
 
@@ -875,18 +988,47 @@ module Sinatra
875
988
  (class << self; self; end).
876
989
  send :define_method, message, &block
877
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
878
1016
  end
879
1017
 
1018
+ reset!
1019
+
880
1020
  set :raise_errors, true
881
1021
  set :dump_errors, false
882
1022
  set :clean_trace, true
1023
+ set :show_exceptions, false
883
1024
  set :sessions, false
884
1025
  set :logging, false
885
1026
  set :methodoverride, false
886
1027
  set :static, false
887
1028
  set :environment, (ENV['RACK_ENV'] || :development).to_sym
888
1029
 
889
- set :run, false
1030
+ set :run, false # start server via at-exit hook?
1031
+ set :running, false # is the built-in server running now?
890
1032
  set :server, %w[thin mongrel webrick]
891
1033
  set :host, '0.0.0.0'
892
1034
  set :port, 4567
@@ -895,18 +1037,7 @@ module Sinatra
895
1037
  set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
896
1038
  set :views, Proc.new { root && File.join(root, 'views') }
897
1039
  set :public, Proc.new { root && File.join(root, 'public') }
898
- set :reload, Proc.new { app_file? && app_file !~ /\.ru$/i && development? }
899
- set :lock, Proc.new { reload? }
900
-
901
- # static files route
902
- get(/.*[^\/]$/) do
903
- pass unless options.static? && options.public?
904
- public_dir = File.expand_path(options.public)
905
- path = File.expand_path(public_dir + unescape(request.path_info))
906
- pass if path[0, public_dir.length] != public_dir
907
- pass unless File.file?(path)
908
- send_file path, :disposition => nil
909
- end
1040
+ set :lock, false
910
1041
 
911
1042
  error ::Exception do
912
1043
  response.status = 500
@@ -922,6 +1053,8 @@ module Sinatra
922
1053
  end
923
1054
 
924
1055
  error NotFound do
1056
+ content_type 'text/html'
1057
+
925
1058
  (<<-HTML).gsub(/^ {8}/, '')
926
1059
  <!DOCTYPE html>
927
1060
  <html>
@@ -943,41 +1076,13 @@ module Sinatra
943
1076
  </html>
944
1077
  HTML
945
1078
  end
946
-
947
- error do
948
- next unless err = request.env['sinatra.error']
949
- heading = err.class.name + ' - ' + err.message.to_s
950
- (<<-HTML).gsub(/^ {8}/, '')
951
- <!DOCTYPE html>
952
- <html>
953
- <head>
954
- <style type="text/css">
955
- body {font-family:verdana;color:#333}
956
- #c {margin-left:20px}
957
- h1 {color:#1D6B8D;margin:0;margin-top:-30px}
958
- h2 {color:#1D6B8D;font-size:18px}
959
- pre {border-left:2px solid #ddd;padding-left:10px;color:#000}
960
- img {margin-top:10px}
961
- </style>
962
- </head>
963
- <body>
964
- <div id="c">
965
- <img src="/__sinatra__/500.png">
966
- <h1>#{escape_html(heading)}</h1>
967
- <pre>#{escape_html(clean_backtrace(err.backtrace) * "\n")}</pre>
968
- <h2>Params</h2>
969
- <pre>#{escape_html(params.inspect)}</pre>
970
- </div>
971
- </body>
972
- </html>
973
- HTML
974
- end
975
1079
  end
976
1080
  end
977
1081
 
978
1082
  # Base class for classic style (top-level) applications.
979
1083
  class Default < Base
980
1084
  set :raise_errors, Proc.new { test? }
1085
+ set :show_exceptions, Proc.new { development? }
981
1086
  set :dump_errors, true
982
1087
  set :sessions, false
983
1088
  set :logging, Proc.new { ! test? }
@@ -987,7 +1092,7 @@ module Sinatra
987
1092
 
988
1093
  def self.register(*extensions, &block) #:nodoc:
989
1094
  added_methods = extensions.map {|m| m.public_instance_methods }.flatten
990
- Delegator.delegate *added_methods
1095
+ Delegator.delegate(*added_methods)
991
1096
  super(*extensions, &block)
992
1097
  end
993
1098
  end
@@ -997,24 +1102,29 @@ module Sinatra
997
1102
  class Application < Default
998
1103
  end
999
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.
1000
1108
  module Delegator #:nodoc:
1001
1109
  def self.delegate(*methods)
1002
1110
  methods.each do |method_name|
1003
1111
  eval <<-RUBY, binding, '(__DELEGATE__)', 1
1004
1112
  def #{method_name}(*args, &b)
1005
- ::Sinatra::Application.#{method_name}(*args, &b)
1113
+ ::Sinatra::Application.send(#{method_name.inspect}, *args, &b)
1006
1114
  end
1007
- private :#{method_name}
1115
+ private #{method_name.inspect}
1008
1116
  RUBY
1009
1117
  end
1010
1118
  end
1011
1119
 
1012
- delegate :get, :put, :post, :delete, :head, :template, :layout, :before,
1013
- :error, :not_found, :configures, :configure, :set, :set_option,
1014
- :set_options, :enable, :disable, :use, :development?, :test?,
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?,
1015
1123
  :production?, :use_in_file_templates!, :helpers
1016
1124
  end
1017
1125
 
1126
+ # Create a new Sinatra application. The block is evaluated in the new app's
1127
+ # class scope.
1018
1128
  def self.new(base=Base, options={}, &block)
1019
1129
  base = Class.new(base)
1020
1130
  base.send :class_eval, &block if block_given?
@@ -1031,13 +1141,3 @@ module Sinatra
1031
1141
  Default.helpers(*extensions, &block)
1032
1142
  end
1033
1143
  end
1034
-
1035
- class String #:nodoc:
1036
- # Define String#each under 1.9 for Rack compatibility. This should be
1037
- # removed once Rack is fully 1.9 compatible.
1038
- alias_method :each, :each_line unless ''.respond_to? :each
1039
-
1040
- # Define String#bytesize as an alias to String#length for Ruby 1.8.6 and
1041
- # earlier.
1042
- alias_method :bytesize, :length unless ''.respond_to? :bytesize
1043
- end