darkhelmet-sinatra 0.9.1.1 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
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