sinatra 0.3.3 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sinatra might be problematic. Click here for more details.

Files changed (82) hide show
  1. data/AUTHORS +40 -0
  2. data/CHANGES +189 -0
  3. data/README.rdoc +146 -117
  4. data/Rakefile +33 -10
  5. data/{test → compat}/app_test.rb +11 -10
  6. data/{test → compat}/application_test.rb +10 -5
  7. data/compat/builder_test.rb +101 -0
  8. data/{test → compat}/custom_error_test.rb +0 -0
  9. data/compat/erb_test.rb +136 -0
  10. data/{test → compat}/events_test.rb +16 -3
  11. data/compat/filter_test.rb +30 -0
  12. data/compat/haml_test.rb +233 -0
  13. data/compat/helper.rb +30 -0
  14. data/compat/mapped_error_test.rb +72 -0
  15. data/{test → compat}/pipeline_test.rb +9 -4
  16. data/{test → compat}/public/foo.xml +0 -0
  17. data/compat/sass_test.rb +57 -0
  18. data/{test → compat}/sessions_test.rb +0 -0
  19. data/{test → compat}/streaming_test.rb +4 -1
  20. data/{test → compat}/sym_params_test.rb +0 -0
  21. data/{test → compat}/template_test.rb +0 -0
  22. data/{test → compat}/use_in_file_templates_test.rb +0 -0
  23. data/{test → compat}/views/foo.builder +0 -0
  24. data/{test → compat}/views/foo.erb +0 -0
  25. data/{test → compat}/views/foo.haml +0 -0
  26. data/{test → compat}/views/foo.sass +0 -0
  27. data/{test → compat}/views/foo_layout.erb +0 -0
  28. data/{test → compat}/views/foo_layout.haml +0 -0
  29. data/{test → compat}/views/layout_test/foo.builder +0 -0
  30. data/{test → compat}/views/layout_test/foo.erb +0 -0
  31. data/{test → compat}/views/layout_test/foo.haml +0 -0
  32. data/{test → compat}/views/layout_test/foo.sass +0 -0
  33. data/{test → compat}/views/layout_test/layout.builder +0 -0
  34. data/{test → compat}/views/layout_test/layout.erb +0 -0
  35. data/{test → compat}/views/layout_test/layout.haml +0 -0
  36. data/{test → compat}/views/layout_test/layout.sass +0 -0
  37. data/{test → compat}/views/no_layout/no_layout.builder +0 -0
  38. data/{test → compat}/views/no_layout/no_layout.haml +0 -0
  39. data/lib/sinatra.rb +6 -1484
  40. data/lib/sinatra/base.rb +838 -0
  41. data/lib/sinatra/compat.rb +239 -0
  42. data/{images → lib/sinatra/images}/404.png +0 -0
  43. data/{images → lib/sinatra/images}/500.png +0 -0
  44. data/lib/sinatra/main.rb +48 -0
  45. data/lib/sinatra/test.rb +114 -0
  46. data/lib/sinatra/test/bacon.rb +17 -0
  47. data/lib/sinatra/test/rspec.rb +7 -8
  48. data/lib/sinatra/test/spec.rb +3 -4
  49. data/lib/sinatra/test/unit.rb +3 -5
  50. data/sinatra.gemspec +68 -35
  51. data/test/base_test.rb +68 -0
  52. data/test/builder_test.rb +50 -87
  53. data/test/data/reload_app_file.rb +3 -0
  54. data/test/erb_test.rb +38 -124
  55. data/test/filter_test.rb +27 -22
  56. data/test/haml_test.rb +51 -216
  57. data/test/helper.rb +22 -6
  58. data/test/helpers_test.rb +361 -0
  59. data/test/mapped_error_test.rb +137 -49
  60. data/test/middleware_test.rb +58 -0
  61. data/test/options_test.rb +97 -0
  62. data/test/reload_test.rb +61 -0
  63. data/test/request_test.rb +18 -0
  64. data/test/result_test.rb +88 -0
  65. data/test/routing_test.rb +391 -0
  66. data/test/sass_test.rb +27 -48
  67. data/test/sinatra_test.rb +13 -0
  68. data/test/static_test.rb +57 -0
  69. data/test/templates_test.rb +88 -0
  70. data/test/views/hello.builder +1 -0
  71. data/test/views/hello.erb +1 -0
  72. data/test/views/hello.haml +1 -0
  73. data/test/views/hello.sass +2 -0
  74. data/test/views/hello.test +1 -0
  75. data/test/views/layout2.builder +3 -0
  76. data/test/views/layout2.erb +2 -0
  77. data/test/views/layout2.haml +2 -0
  78. data/test/views/layout2.test +1 -0
  79. metadata +80 -48
  80. data/ChangeLog +0 -96
  81. data/lib/sinatra/test/methods.rb +0 -76
  82. data/test/event_context_test.rb +0 -15
File without changes
@@ -62,6 +62,7 @@ context "Static files (by default)" do
62
62
  headers['Last-Modified'].should.equal last_modified.httpdate
63
63
  end
64
64
 
65
+ # Deprecated. Use: ConditionalGet middleware.
65
66
  specify "are not served when If-Modified-Since matches" do
66
67
  last_modified = File.mtime(Sinatra.application.options.public + '/foo.xml')
67
68
  @request = Rack::MockRequest.new(Sinatra.application)
@@ -91,7 +92,8 @@ context "SendData" do
91
92
  Sinatra.application = nil
92
93
  end
93
94
 
94
- specify "should send the data with options" do
95
+ # Deprecated. send_data is going away.
96
+ xspecify "should send the data with options" do
95
97
  get '/' do
96
98
  send_data 'asdf', :status => 500
97
99
  end
@@ -102,6 +104,7 @@ context "SendData" do
102
104
  body.should.equal 'asdf'
103
105
  end
104
106
 
107
+ # Deprecated. The Content-Disposition is no longer handled by sendfile.
105
108
  specify "should include a Content-Disposition header" do
106
109
  get '/' do
107
110
  send_file File.dirname(__FILE__) + '/public/foo.xml'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,1486 +1,8 @@
1
- require 'time'
2
- require 'ostruct'
3
- require 'uri'
4
- require 'rack'
1
+ libdir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
5
3
 
6
- if ENV['SWIFT']
7
- require 'swiftcore/swiftiplied_mongrel'
8
- puts "Using Swiftiplied Mongrel"
9
- elsif ENV['EVENT']
10
- require 'swiftcore/evented_mongrel'
11
- puts "Using Evented Mongrel"
12
- end
4
+ require 'sinatra/base'
5
+ require 'sinatra/main'
6
+ require 'sinatra/compat'
13
7
 
14
- unless defined?(Rack::File::MIME_TYPES)
15
- class Rack::File; MIME_TYPES = Rack::Mime::MIME_TYPES; end
16
- end
17
-
18
- module Rack #:nodoc:
19
-
20
- class Request #:nodoc:
21
-
22
- # Set of request method names allowed via the _method parameter hack. By
23
- # default, all request methods defined in RFC2616 are included, with the
24
- # exception of TRACE and CONNECT.
25
- POST_TUNNEL_METHODS_ALLOWED = %w( PUT DELETE OPTIONS HEAD )
26
-
27
- # Return the HTTP request method with support for method tunneling using
28
- # the POST _method parameter hack. If the real request method is POST and
29
- # a _method param is given and the value is one defined in
30
- # +POST_TUNNEL_METHODS_ALLOWED+, return the value of the _method param
31
- # instead.
32
- def request_method
33
- if post_tunnel_method_hack?
34
- params['_method'].upcase
35
- else
36
- @env['REQUEST_METHOD']
37
- end
38
- end
39
-
40
- def user_agent
41
- @env['HTTP_USER_AGENT']
42
- end
43
-
44
- private
45
-
46
- # Return truthfully if the request is a valid verb-over-post hack.
47
- def post_tunnel_method_hack?
48
- @env['REQUEST_METHOD'] == 'POST' &&
49
- POST_TUNNEL_METHODS_ALLOWED.include?(self.POST.fetch('_method', '').upcase)
50
- end
51
- end
52
-
53
- module Utils
54
- extend self
55
- end
56
- end
57
-
58
-
59
- module Sinatra
60
- extend self
61
-
62
- VERSION = '0.3.3'
63
-
64
- class NotFound < RuntimeError
65
- def self.code ; 404 ; end
66
- end
67
- class ServerError < RuntimeError
68
- def self.code ; 500 ; end
69
- end
70
-
71
- Result = Struct.new(:block, :params, :status) unless defined?(Result)
72
-
73
- def options
74
- application.options
75
- end
76
-
77
- def application
78
- @app ||= Application.new
79
- end
80
-
81
- def application=(app)
82
- @app = app
83
- end
84
-
85
- def port
86
- application.options.port
87
- end
88
-
89
- def host
90
- application.options.host
91
- end
92
-
93
- def env
94
- application.options.env
95
- end
96
-
97
- # Deprecated: use application instead of build_application.
98
- alias :build_application :application
99
-
100
- def server
101
- options.server ||= defined?(Rack::Handler::Thin) ? "thin" : "mongrel"
102
-
103
- # Convert the server into the actual handler name
104
- handler = options.server.capitalize
105
-
106
- # If the convenience conversion didn't get us anything,
107
- # fall back to what the user actually set.
108
- handler = options.server unless Rack::Handler.const_defined?(handler)
109
-
110
- @server ||= eval("Rack::Handler::#{handler}")
111
- end
112
-
113
- def run
114
- begin
115
- puts "== Sinatra/#{Sinatra::VERSION} has taken the stage on port #{port} for #{env} with backup by #{server.name}"
116
- server.run(application, {:Port => port, :Host => host}) do |server|
117
- trap(:INT) do
118
- server.stop
119
- puts "\n== Sinatra has ended his set (crowd applauds)"
120
- end
121
- end
122
- rescue Errno::EADDRINUSE => e
123
- puts "== Someone is already performing on port #{port}!"
124
- end
125
- end
126
-
127
- class Event
128
- include Rack::Utils
129
-
130
- URI_CHAR = '[^/?:,&#\.]'.freeze unless defined?(URI_CHAR)
131
- PARAM = /(:(#{URI_CHAR}+)|\*)/.freeze unless defined?(PARAM)
132
- SPLAT = /(.*?)/
133
- attr_reader :path, :block, :param_keys, :pattern, :options
134
-
135
- def initialize(path, options = {}, &b)
136
- @path = URI.encode(path)
137
- @block = b
138
- @param_keys = []
139
- @options = options
140
- splats = 0
141
- regex = @path.to_s.gsub(PARAM) do |match|
142
- if match == "*"
143
- @param_keys << "_splat_#{splats}"
144
- splats += 1
145
- SPLAT.to_s
146
- else
147
- @param_keys << $2
148
- "(#{URI_CHAR}+)"
149
- end
150
- end
151
-
152
- @pattern = /^#{regex}$/
153
- end
154
-
155
- def invoke(request)
156
- params = {}
157
- if agent = options[:agent]
158
- return unless request.user_agent =~ agent
159
- params[:agent] = $~[1..-1]
160
- end
161
- if host = options[:host]
162
- return unless host === request.host
163
- end
164
- return unless pattern =~ request.path_info.squeeze('/')
165
- path_params = param_keys.zip($~.captures.map{|s| unescape(s) if s}).to_hash
166
- params.merge!(path_params)
167
- splats = params.select { |k, v| k =~ /^_splat_\d+$/ }.sort.map(&:last)
168
- unless splats.empty?
169
- params.delete_if { |k, v| k =~ /^_splat_\d+$/ }
170
- params["splat"] = splats
171
- end
172
- Result.new(block, params, 200)
173
- end
174
-
175
- end
176
-
177
- class Error
178
-
179
- attr_reader :type, :block, :options
180
-
181
- def initialize(type, options={}, &block)
182
- @type = type
183
- @block = block
184
- @options = options
185
- end
186
-
187
- def invoke(request)
188
- Result.new(block, options, code)
189
- end
190
-
191
- def code
192
- if type.respond_to?(:code)
193
- type.code
194
- else
195
- 500
196
- end
197
- end
198
-
199
- end
200
-
201
- class Static
202
- include Rack::Utils
203
-
204
- def initialize(app)
205
- @app = app
206
- end
207
-
208
- def invoke(request)
209
- path = @app.options.public + unescape(request.path_info)
210
- return unless File.file?(path)
211
- block = Proc.new { send_file path, :disposition => nil }
212
- Result.new(block, {}, 200)
213
- end
214
- end
215
-
216
- # Methods for sending files and streams to the browser instead of rendering.
217
- module Streaming
218
- DEFAULT_SEND_FILE_OPTIONS = {
219
- :type => 'application/octet-stream'.freeze,
220
- :disposition => 'attachment'.freeze,
221
- :stream => true,
222
- :buffer_size => 8192
223
- }.freeze
224
-
225
- class MissingFile < RuntimeError; end
226
-
227
- class FileStreamer
228
- attr_reader :path, :options
229
-
230
- def initialize(path, options)
231
- @path, @options = path, options
232
- end
233
-
234
- def to_result(cx, *args)
235
- self
236
- end
237
-
238
- def each
239
- size = options[:buffer_size]
240
- File.open(path, 'rb') do |file|
241
- while buf = file.read(size)
242
- yield buf
243
- end
244
- end
245
- end
246
- end
247
-
248
- protected
249
- # Sends the file by streaming it 8192 bytes at a time. This way the
250
- # whole file doesn't need to be read into memory at once. This makes
251
- # it feasible to send even large files.
252
- #
253
- # Be careful to sanitize the path parameter if it coming from a web
254
- # page. send_file(params[:path]) allows a malicious user to
255
- # download any file on your server.
256
- #
257
- # Options:
258
- # * <tt>:filename</tt> - suggests a filename for the browser to use.
259
- # Defaults to File.basename(path).
260
- # * <tt>:type</tt> - specifies an HTTP content type.
261
- # Defaults to 'application/octet-stream'.
262
- # * <tt>:disposition</tt> - specifies whether the file will be shown
263
- # inline or downloaded. Valid values are 'inline' and 'attachment'
264
- # (default). When set to nil, the Content-Disposition and
265
- # Content-Transfer-Encoding headers are omitted entirely.
266
- # * <tt>:stream</tt> - whether to send the file to the user agent as it
267
- # is read (true) or to read the entire file before sending (false).
268
- # Defaults to true.
269
- # * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used
270
- # to stream the file. Defaults to 8192.
271
- # * <tt>:status</tt> - specifies the status code to send with the
272
- # response. Defaults to '200 OK'.
273
- # * <tt>:last_modified</tt> - an optional RFC 2616 formatted date value
274
- # (See Time#httpdate) indicating the last modified time of the file.
275
- # If the request includes an If-Modified-Since header that matches this
276
- # value exactly, a 304 Not Modified response is sent instead of the file.
277
- # Defaults to the file's last modified time.
278
- #
279
- # The default Content-Type and Content-Disposition headers are
280
- # set to download arbitrary binary files in as many browsers as
281
- # possible. IE versions 4, 5, 5.5, and 6 are all known to have
282
- # a variety of quirks (especially when downloading over SSL).
283
- #
284
- # Simple download:
285
- # send_file '/path/to.zip'
286
- #
287
- # Show a JPEG in the browser:
288
- # send_file '/path/to.jpeg',
289
- # :type => 'image/jpeg',
290
- # :disposition => 'inline'
291
- #
292
- # Show a 404 page in the browser:
293
- # send_file '/path/to/404.html,
294
- # :type => 'text/html; charset=utf-8',
295
- # :status => 404
296
- #
297
- # Read about the other Content-* HTTP headers if you'd like to
298
- # provide the user with more information (such as Content-Description).
299
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
300
- #
301
- # Also be aware that the document may be cached by proxies and browsers.
302
- # The Pragma and Cache-Control headers declare how the file may be cached
303
- # by intermediaries. They default to require clients to validate with
304
- # the server before releasing cached responses. See
305
- # http://www.mnot.net/cache_docs/ for an overview of web caching and
306
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
307
- # for the Cache-Control header spec.
308
- def send_file(path, options = {}) #:doc:
309
- raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
310
-
311
- options[:length] ||= File.size(path)
312
- options[:filename] ||= File.basename(path)
313
- options[:type] ||= Rack::File::MIME_TYPES[File.extname(options[:filename])[1..-1]] || 'text/plain'
314
- options[:last_modified] ||= File.mtime(path).httpdate
315
- options[:stream] = true unless options.key?(:stream)
316
- options[:buffer_size] ||= DEFAULT_SEND_FILE_OPTIONS[:buffer_size]
317
- send_file_headers! options
318
-
319
- if options[:stream]
320
- throw :halt, [options[:status] || 200, FileStreamer.new(path, options)]
321
- else
322
- File.open(path, 'rb') { |file| throw :halt, [options[:status] || 200, [file.read]] }
323
- end
324
- end
325
-
326
- # Send binary data to the user as a file download. May set content type,
327
- # apparent file name, and specify whether to show data inline or download
328
- # as an attachment.
329
- #
330
- # Options:
331
- # * <tt>:filename</tt> - Suggests a filename for the browser to use.
332
- # * <tt>:type</tt> - specifies an HTTP content type.
333
- # Defaults to 'application/octet-stream'.
334
- # * <tt>:disposition</tt> - specifies whether the file will be shown inline
335
- # or downloaded. Valid values are 'inline' and 'attachment' (default).
336
- # * <tt>:status</tt> - specifies the status code to send with the response.
337
- # Defaults to '200 OK'.
338
- # * <tt>:last_modified</tt> - an optional RFC 2616 formatted date value (See
339
- # Time#httpdate) indicating the last modified time of the response entity.
340
- # If the request includes an If-Modified-Since header that matches this
341
- # value exactly, a 304 Not Modified response is sent instead of the data.
342
- #
343
- # Generic data download:
344
- # send_data buffer
345
- #
346
- # Download a dynamically-generated tarball:
347
- # send_data generate_tgz('dir'), :filename => 'dir.tgz'
348
- #
349
- # Display an image Active Record in the browser:
350
- # send_data image.data,
351
- # :type => image.content_type,
352
- # :disposition => 'inline'
353
- #
354
- # See +send_file+ for more information on HTTP Content-* headers and caching.
355
- def send_data(data, options = {}) #:doc:
356
- send_file_headers! options.merge(:length => data.size)
357
- throw :halt, [options[:status] || 200, [data]]
358
- end
359
-
360
- private
361
-
362
- def send_file_headers!(options)
363
- options = DEFAULT_SEND_FILE_OPTIONS.merge(options)
364
- [:length, :type, :disposition].each do |arg|
365
- raise ArgumentError, ":#{arg} option required" unless options.key?(arg)
366
- end
367
-
368
- # Send a "304 Not Modified" if the last_modified option is provided and
369
- # matches the If-Modified-Since request header value.
370
- if last_modified = options[:last_modified]
371
- header 'Last-Modified' => last_modified
372
- throw :halt, [ 304, '' ] if last_modified == request.env['HTTP_IF_MODIFIED_SINCE']
373
- end
374
-
375
- headers(
376
- 'Content-Length' => options[:length].to_s,
377
- 'Content-Type' => options[:type].strip # fixes a problem with extra '\r' with some browsers
378
- )
379
-
380
- # Omit Content-Disposition and Content-Transfer-Encoding headers if
381
- # the :disposition option set to nil.
382
- if !options[:disposition].nil?
383
- disposition = options[:disposition].dup || 'attachment'
384
- disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
385
- headers 'Content-Disposition' => disposition, 'Content-Transfer-Encoding' => 'binary'
386
- end
387
-
388
- # Fix a problem with IE 6.0 on opening downloaded files:
389
- # If Cache-Control: no-cache is set (which Rails does by default),
390
- # IE removes the file it just downloaded from its cache immediately
391
- # after it displays the "open/save" dialog, which means that if you
392
- # hit "open" the file isn't there anymore when the application that
393
- # is called for handling the download is run, so let's workaround that
394
- header('Cache-Control' => 'private') if headers['Cache-Control'] == 'no-cache'
395
- end
396
- end
397
-
398
-
399
- # Helper methods for building various aspects of the HTTP response.
400
- module ResponseHelpers
401
-
402
- # Immediately halt response execution by redirecting to the resource
403
- # specified. The +path+ argument may be an absolute URL or a path
404
- # relative to the site root. Additional arguments are passed to the
405
- # halt.
406
- #
407
- # With no integer status code, a '302 Temporary Redirect' response is
408
- # sent. To send a permanent redirect, pass an explicit status code of
409
- # 301:
410
- #
411
- # redirect '/somewhere/else', 301
412
- #
413
- # NOTE: No attempt is made to rewrite the path based on application
414
- # context. The 'Location' response header is set verbatim to the value
415
- # provided.
416
- def redirect(path, *args)
417
- status(302)
418
- header 'Location' => path
419
- throw :halt, *args
420
- end
421
-
422
- # Access or modify response headers. With no argument, return the
423
- # underlying headers Hash. With a Hash argument, add or overwrite
424
- # existing response headers with the values provided:
425
- #
426
- # headers 'Content-Type' => "text/html;charset=utf-8",
427
- # 'Last-Modified' => Time.now.httpdate,
428
- # 'X-UA-Compatible' => 'IE=edge'
429
- #
430
- # This method also available in singular form (#header).
431
- def headers(header = nil)
432
- @response.headers.merge!(header) if header
433
- @response.headers
434
- end
435
- alias :header :headers
436
-
437
- # Set the content type of the response body (HTTP 'Content-Type' header).
438
- #
439
- # The +type+ argument may be an internet media type (e.g., 'text/html',
440
- # 'application/xml+atom', 'image/png') or a Symbol key into the
441
- # Rack::File::MIME_TYPES table.
442
- #
443
- # Media type parameters, such as "charset", may also be specified using the
444
- # optional hash argument:
445
- #
446
- # get '/foo.html' do
447
- # content_type 'text/html', :charset => 'utf-8'
448
- # "<h1>Hello World</h1>"
449
- # end
450
- #
451
- def content_type(type, params={})
452
- type = Rack::File::MIME_TYPES[type.to_s] if type.kind_of?(Symbol)
453
- fail "Invalid or undefined media_type: #{type}" if type.nil?
454
- if params.any?
455
- params = params.collect { |kv| "%s=%s" % kv }.join(', ')
456
- type = [ type, params ].join(";")
457
- end
458
- response.header['Content-Type'] = type
459
- end
460
-
461
- # Set the last modified time of the resource (HTTP 'Last-Modified' header)
462
- # and halt if conditional GET matches. The +time+ argument is a Time,
463
- # DateTime, or other object that responds to +to_time+.
464
- #
465
- # When the current request includes an 'If-Modified-Since' header that
466
- # matches the time specified, execution is immediately halted with a
467
- # '304 Not Modified' response.
468
- #
469
- # Calling this method before perfoming heavy processing (e.g., lengthy
470
- # database queries, template rendering, complex logic) can dramatically
471
- # increase overall throughput with caching clients.
472
- def last_modified(time)
473
- time = time.to_time if time.respond_to?(:to_time)
474
- time = time.httpdate if time.respond_to?(:httpdate)
475
- response.header['Last-Modified'] = time
476
- throw :halt, 304 if time == request.env['HTTP_IF_MODIFIED_SINCE']
477
- time
478
- end
479
-
480
- # Set the response entity tag (HTTP 'ETag' header) and halt if conditional
481
- # GET matches. The +value+ argument is an identifier that uniquely
482
- # identifies the current version of the resource. The +strength+ argument
483
- # indicates whether the etag should be used as a :strong (default) or :weak
484
- # cache validator.
485
- #
486
- # When the current request includes an 'If-None-Match' header with a
487
- # matching etag, execution is immediately halted. If the request method is
488
- # GET or HEAD, a '304 Not Modified' response is sent. For all other request
489
- # methods, a '412 Precondition Failed' response is sent.
490
- #
491
- # Calling this method before perfoming heavy processing (e.g., lengthy
492
- # database queries, template rendering, complex logic) can dramatically
493
- # increase overall throughput with caching clients.
494
- #
495
- # ==== See Also
496
- # {RFC2616: ETag}[http://w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19],
497
- # ResponseHelpers#last_modified
498
- def entity_tag(value, strength=:strong)
499
- value =
500
- case strength
501
- when :strong then '"%s"' % value
502
- when :weak then 'W/"%s"' % value
503
- else raise TypeError, "strength must be one of :strong or :weak"
504
- end
505
- response.header['ETag'] = value
506
-
507
- # Check for If-None-Match request header and halt if match is found.
508
- etags = (request.env['HTTP_IF_NONE_MATCH'] || '').split(/\s*,\s*/)
509
- if etags.include?(value) || etags.include?('*')
510
- # GET/HEAD requests: send Not Modified response
511
- throw :halt, 304 if request.get? || request.head?
512
- # Other requests: send Precondition Failed response
513
- throw :halt, 412
514
- end
515
- end
516
-
517
- alias :etag :entity_tag
518
-
519
- end
520
-
521
- module RenderingHelpers
522
-
523
- def render(renderer, template, options={})
524
- m = method("render_#{renderer}")
525
- result = m.call(resolve_template(renderer, template, options), options)
526
- if layout = determine_layout(renderer, template, options)
527
- result = m.call(resolve_template(renderer, layout, options), options) { result }
528
- end
529
- result
530
- end
531
-
532
- def determine_layout(renderer, template, options)
533
- return if options[:layout] == false
534
- layout_from_options = options[:layout] || :layout
535
- resolve_template(renderer, layout_from_options, options, false)
536
- end
537
-
538
- private
539
-
540
- def resolve_template(renderer, template, options, scream = true)
541
- case template
542
- when String
543
- template
544
- when Proc
545
- template.call
546
- when Symbol
547
- if proc = templates[template]
548
- resolve_template(renderer, proc, options, scream)
549
- else
550
- read_template_file(renderer, template, options, scream)
551
- end
552
- else
553
- nil
554
- end
555
- end
556
-
557
- def read_template_file(renderer, template, options, scream = true)
558
- path = File.join(
559
- options[:views_directory] || Sinatra.application.options.views,
560
- "#{template}.#{renderer}"
561
- )
562
- unless File.exists?(path)
563
- raise Errno::ENOENT.new(path) if scream
564
- nil
565
- else
566
- File.read(path)
567
- end
568
- end
569
-
570
- def templates
571
- Sinatra.application.templates
572
- end
573
-
574
- end
575
-
576
- module Erb
577
-
578
- def erb(content, options={})
579
- require 'erb'
580
- render(:erb, content, options)
581
- end
582
-
583
- private
584
-
585
- def render_erb(content, options = {})
586
- locals_opt = options.delete(:locals) || {}
587
-
588
- locals_code = ""
589
- locals_hash = {}
590
- locals_opt.each do |key, value|
591
- locals_code << "#{key} = locals_hash[:#{key}]\n"
592
- locals_hash[:"#{key}"] = value
593
- end
594
-
595
- body = ::ERB.new(content).src
596
- eval("#{locals_code}#{body}", binding)
597
- end
598
-
599
- end
600
-
601
- module Haml
602
-
603
- def haml(content, options={})
604
- require 'haml'
605
- render(:haml, content, options)
606
- end
607
-
608
- private
609
-
610
- def render_haml(content, options = {}, &b)
611
- haml_options = (options[:options] || {}).
612
- merge(Sinatra.options.haml || {})
613
- ::Haml::Engine.new(content, haml_options).
614
- render(options[:scope] || self, options[:locals] || {}, &b)
615
- end
616
-
617
- end
618
-
619
- # Generate valid CSS using Sass (part of Haml)
620
- #
621
- # Sass templates can be in external files with <tt>.sass</tt> extension
622
- # or can use Sinatra's in_file_templates. In either case, the file can
623
- # be rendered by passing the name of the template to the +sass+ method
624
- # as a symbol.
625
- #
626
- # Unlike Haml, Sass does not support a layout file, so the +sass+ method
627
- # will ignore both the default <tt>layout.sass</tt> file and any parameters
628
- # passed in as <tt>:layout</tt> in the options hash.
629
- #
630
- # === Sass Template Files
631
- #
632
- # Sass templates can be stored in separate files with a <tt>.sass</tt>
633
- # extension under the view path.
634
- #
635
- # Example:
636
- # get '/stylesheet.css' do
637
- # header 'Content-Type' => 'text/css; charset=utf-8'
638
- # sass :stylesheet
639
- # end
640
- #
641
- # The "views/stylesheet.sass" file might contain the following:
642
- #
643
- # body
644
- # #admin
645
- # :background-color #CCC
646
- # #main
647
- # :background-color #000
648
- # #form
649
- # :border-color #AAA
650
- # :border-width 10px
651
- #
652
- # And yields the following output:
653
- #
654
- # body #admin {
655
- # background-color: #CCC; }
656
- # body #main {
657
- # background-color: #000; }
658
- #
659
- # #form {
660
- # border-color: #AAA;
661
- # border-width: 10px; }
662
- #
663
- #
664
- # NOTE: Haml must be installed or a LoadError will be raised the first time an
665
- # attempt is made to render a Sass template.
666
- #
667
- # See http://haml.hamptoncatlin.com/docs/rdoc/classes/Sass.html for comprehensive documentation on Sass.
668
- module Sass
669
-
670
- def sass(content, options = {})
671
- require 'sass'
672
-
673
- # Sass doesn't support a layout, so we override any possible layout here
674
- options[:layout] = false
675
-
676
- render(:sass, content, options)
677
- end
678
-
679
- private
680
-
681
- def render_sass(content, options = {})
682
- ::Sass::Engine.new(content).render
683
- end
684
-
685
- end
686
-
687
- # Generating conservative XML content using Builder templates.
688
- #
689
- # Builder templates can be inline by passing a block to the builder method,
690
- # or in external files with +.builder+ extension by passing the name of the
691
- # template to the +builder+ method as a Symbol.
692
- #
693
- # === Inline Rendering
694
- #
695
- # If the builder method is given a block, the block is called directly with
696
- # an +XmlMarkup+ instance and the result is returned as String:
697
- # get '/who.xml' do
698
- # builder do |xml|
699
- # xml.instruct!
700
- # xml.person do
701
- # xml.name "Francis Albert Sinatra",
702
- # :aka => "Frank Sinatra"
703
- # xml.email 'frank@capitolrecords.com'
704
- # end
705
- # end
706
- # end
707
- #
708
- # Yields the following XML:
709
- # <?xml version='1.0' encoding='UTF-8'?>
710
- # <person>
711
- # <name aka='Frank Sinatra'>Francis Albert Sinatra</name>
712
- # <email>Frank Sinatra</email>
713
- # </person>
714
- #
715
- # === Builder Template Files
716
- #
717
- # Builder templates can be stored in separate files with a +.builder+
718
- # extension under the view path. An +XmlMarkup+ object named +xml+ is
719
- # automatically made available to template.
720
- #
721
- # Example:
722
- # get '/bio.xml' do
723
- # builder :bio
724
- # end
725
- #
726
- # The "views/bio.builder" file might contain the following:
727
- # xml.instruct! :xml, :version => '1.1'
728
- # xml.person do
729
- # xml.name "Francis Albert Sinatra"
730
- # xml.aka "Frank Sinatra"
731
- # xml.aka "Ol' Blue Eyes"
732
- # xml.aka "The Chairman of the Board"
733
- # xml.born 'date' => '1915-12-12' do
734
- # xml.text! "Hoboken, New Jersey, U.S.A."
735
- # end
736
- # xml.died 'age' => 82
737
- # end
738
- #
739
- # And yields the following output:
740
- # <?xml version='1.1' encoding='UTF-8'?>
741
- # <person>
742
- # <name>Francis Albert Sinatra</name>
743
- # <aka>Frank Sinatra</aka>
744
- # <aka>Ol&apos; Blue Eyes</aka>
745
- # <aka>The Chairman of the Board</aka>
746
- # <born date='1915-12-12'>Hoboken, New Jersey, U.S.A.</born>
747
- # <died age='82' />
748
- # </person>
749
- #
750
- # NOTE: Builder must be installed or a LoadError will be raised the first
751
- # time an attempt is made to render a builder template.
752
- #
753
- # See http://builder.rubyforge.org/ for comprehensive documentation on
754
- # Builder.
755
- module Builder
756
-
757
- def builder(content=nil, options={}, &block)
758
- options, content = content, nil if content.is_a?(Hash)
759
- content = Proc.new { block } if content.nil?
760
- render(:builder, content, options)
761
- end
762
-
763
- private
764
-
765
- def render_builder(content, options = {}, &b)
766
- require 'builder'
767
- xml = ::Builder::XmlMarkup.new(:indent => 2)
768
- case content
769
- when String
770
- eval(content, binding, '<BUILDER>', 1)
771
- when Proc
772
- content.call(xml)
773
- end
774
- xml.target!
775
- end
776
-
777
- end
778
-
779
- class EventContext
780
- include Rack::Utils
781
- include ResponseHelpers
782
- include Streaming
783
- include RenderingHelpers
784
- include Erb
785
- include Haml
786
- include Builder
787
- include Sass
788
-
789
- attr_accessor :request, :response
790
-
791
- attr_accessor :route_params
792
-
793
- def initialize(request, response, route_params)
794
- @params = nil
795
- @data = nil
796
- @request = request
797
- @response = response
798
- @route_params = route_params
799
- @response.body = nil
800
- end
801
-
802
- def status(value=nil)
803
- response.status = value if value
804
- response.status
805
- end
806
-
807
- def body(value=nil)
808
- response.body = value if value
809
- response.body
810
- end
811
-
812
- def params
813
- @params ||=
814
- begin
815
- hash = Hash.new {|h,k| h[k.to_s] if Symbol === k}
816
- hash.merge! @request.params
817
- hash.merge! @route_params
818
- hash
819
- end
820
- end
821
-
822
- def data
823
- @data ||= params.keys.first
824
- end
825
-
826
- def stop(*args)
827
- throw :halt, args
828
- end
829
-
830
- def complete(returned)
831
- @response.body || returned
832
- end
833
-
834
- def session
835
- request.env['rack.session'] ||= {}
836
- end
837
-
838
- def reset!
839
- @params = nil
840
- @data = nil
841
- end
842
-
843
- private
844
-
845
- def method_missing(name, *args, &b)
846
- if @response.respond_to?(name)
847
- @response.send(name, *args, &b)
848
- else
849
- super
850
- end
851
- end
852
-
853
- end
854
-
855
-
856
- # The Application class represents the top-level working area of a
857
- # Sinatra app. It provides the DSL for defining various aspects of the
858
- # application and implements a Rack compatible interface for dispatching
859
- # requests.
860
- #
861
- # Many of the instance methods defined in this class (#get, #post,
862
- # #put, #delete, #layout, #before, #error, #not_found, etc.) are
863
- # available at top-level scope. When invoked from top-level, the
864
- # messages are forwarded to the "default application" (accessible
865
- # at Sinatra::application).
866
- class Application
867
-
868
- # Hash of event handlers with request method keys and
869
- # arrays of potential handlers as values.
870
- attr_reader :events
871
-
872
- # Hash of error handlers with error status codes as keys and
873
- # handlers as values.
874
- attr_reader :errors
875
-
876
- # Hash of template name mappings.
877
- attr_reader :templates
878
-
879
- # Hash of filters with event name keys (:before) and arrays of
880
- # handlers as values.
881
- attr_reader :filters
882
-
883
- # Array of objects to clear during reload. The objects in this array
884
- # must respond to :clear.
885
- attr_reader :clearables
886
-
887
- # Object including open attribute methods for modifying Application
888
- # configuration.
889
- attr_reader :options
890
-
891
- # List of methods available from top-level scope. When invoked from
892
- # top-level the method is forwarded to the default application
893
- # (Sinatra::application).
894
- FORWARD_METHODS = %w[
895
- get put post delete head template layout before error not_found
896
- configures configure set set_options set_option enable disable use
897
- development? test? production?
898
- ]
899
-
900
- # Create a new Application with a default configuration taken
901
- # from the default_options Hash.
902
- #
903
- # NOTE: A default Application is automatically created the first
904
- # time any of Sinatra's DSL related methods is invoked so there
905
- # is typically no need to create an instance explicitly. See
906
- # Sinatra::application for more information.
907
- def initialize
908
- @reloading = false
909
- @clearables = [
910
- @events = Hash.new { |hash, key| hash[key] = [] },
911
- @errors = Hash.new,
912
- @filters = Hash.new { |hash, key| hash[key] = [] },
913
- @templates = Hash.new,
914
- @middleware = []
915
- ]
916
- @options = OpenStruct.new(self.class.default_options)
917
- load_default_configuration!
918
- end
919
-
920
- # Hash of default application configuration options. When a new
921
- # Application is created, the #options object takes its initial values
922
- # from here.
923
- #
924
- # Changes to the default_options Hash effect only Application objects
925
- # created after the changes are made. For this reason, modifications to
926
- # the default_options Hash typically occur at the very beginning of a
927
- # file, before any DSL related functions are invoked.
928
- def self.default_options
929
- return @default_options unless @default_options.nil?
930
- app_file = locate_app_file
931
- root = File.expand_path(File.dirname(app_file))
932
- @default_options = {
933
- :run => true,
934
- :port => 4567,
935
- :host => '0.0.0.0',
936
- :env => (ENV['RACK_ENV'] || :development).to_sym,
937
- :root => root,
938
- :views => root + '/views',
939
- :public => root + '/public',
940
- :sessions => false,
941
- :logging => true,
942
- :app_file => app_file,
943
- :raise_errors => false
944
- }
945
- load_default_options_from_command_line!
946
- @default_options
947
- end
948
-
949
- # Search ARGV for command line arguments and update the
950
- # Sinatra::default_options Hash accordingly. This method is
951
- # invoked the first time the default_options Hash is accessed.
952
- # NOTE: Ignores --name so unit/spec tests can run individually
953
- def self.load_default_options_from_command_line! #:nodoc:
954
- # fixes issue with: gem install --test sinatra
955
- return if ARGV.empty? || File.basename($0) =~ /gem/
956
- require 'optparse'
957
- OptionParser.new do |op|
958
- op.on('-p port') { |port| default_options[:port] = port }
959
- op.on('-e env') { |env| default_options[:env] = env.to_sym }
960
- op.on('-x') { default_options[:mutex] = true }
961
- op.on('-s server') { |server| default_options[:server] = server }
962
- end.parse!(ARGV.dup.select { |o| o !~ /--name/ })
963
- end
964
-
965
- # Use heuristics to locate the application file.
966
- def self.locate_app_file
967
- caller[1..-1].reverse.each do |path|
968
- path = path.split(':', 2)[0]
969
- next if path =~ /sinatra\.rb$/ || path == '(__DSL__)'
970
- return path
971
- end
972
- $0
973
- end
974
-
975
- # Determine whether the application is in the process of being
976
- # reloaded.
977
- def reloading?
978
- @reloading == true
979
- end
980
-
981
- # Yield to the block for configuration if the current environment
982
- # matches any included in the +envs+ list. Always yield to the block
983
- # when no environment is specified.
984
- #
985
- # NOTE: configuration blocks are not executed during reloads.
986
- def configures(*envs, &b)
987
- return if reloading?
988
- yield self if envs.empty? || envs.include?(options.env)
989
- end
990
-
991
- alias :configure :configures
992
-
993
- # When both +option+ and +value+ arguments are provided, set the option
994
- # specified. With a single Hash argument, set all options specified in
995
- # Hash. Options are available via the Application#options object.
996
- #
997
- # Setting individual options:
998
- # set :port, 80
999
- # set :env, :production
1000
- # set :views, '/path/to/views'
1001
- #
1002
- # Setting multiple options:
1003
- # set :port => 80,
1004
- # :env => :production,
1005
- # :views => '/path/to/views'
1006
- #
1007
- def set(option, value=self)
1008
- if value == self && option.kind_of?(Hash)
1009
- option.each { |key,val| set(key, val) }
1010
- else
1011
- options.send("#{option}=", value)
1012
- end
1013
- end
1014
-
1015
- alias :set_option :set
1016
- alias :set_options :set
1017
-
1018
- # Enable the options specified by setting their values to true. For
1019
- # example, to enable sessions and logging:
1020
- # enable :sessions, :logging
1021
- def enable(*opts)
1022
- opts.each { |key| set(key, true) }
1023
- end
1024
-
1025
- # Disable the options specified by setting their values to false. For
1026
- # example, to disable logging and automatic run:
1027
- # disable :logging, :run
1028
- def disable(*opts)
1029
- opts.each { |key| set(key, false) }
1030
- end
1031
-
1032
- # Define an event handler for the given request method and path
1033
- # spec. The block is executed when a request matches the method
1034
- # and spec.
1035
- #
1036
- # NOTE: The #get, #post, #put, and #delete helper methods should
1037
- # be used to define events when possible.
1038
- def event(method, path, options = {}, &b)
1039
- events[method].push(Event.new(path, options, &b)).last
1040
- end
1041
-
1042
- # Define an event handler for GET requests.
1043
- def get(path, options={}, &b)
1044
- event(:get, path, options, &b)
1045
- end
1046
-
1047
- # Define an event handler for POST requests.
1048
- def post(path, options={}, &b)
1049
- event(:post, path, options, &b)
1050
- end
1051
-
1052
- # Define an event handler for HEAD requests.
1053
- def head(path, options={}, &b)
1054
- event(:head, path, options, &b)
1055
- end
1056
-
1057
- # Define an event handler for PUT requests.
1058
- #
1059
- # NOTE: PUT events are triggered when the HTTP request method is
1060
- # PUT and also when the request method is POST and the body includes a
1061
- # "_method" parameter set to "PUT".
1062
- def put(path, options={}, &b)
1063
- event(:put, path, options, &b)
1064
- end
1065
-
1066
- # Define an event handler for DELETE requests.
1067
- #
1068
- # NOTE: DELETE events are triggered when the HTTP request method is
1069
- # DELETE and also when the request method is POST and the body includes a
1070
- # "_method" parameter set to "DELETE".
1071
- def delete(path, options={}, &b)
1072
- event(:delete, path, options, &b)
1073
- end
1074
-
1075
- # Visits and invokes each handler registered for the +request_method+ in
1076
- # definition order until a Result response is produced. If no handler
1077
- # responds with a Result, the NotFound error handler is invoked.
1078
- #
1079
- # When the request_method is "HEAD" and no valid Result is produced by
1080
- # the set of handlers registered for HEAD requests, an attempt is made to
1081
- # invoke the GET handlers to generate the response before resorting to the
1082
- # default error handler.
1083
- def lookup(request)
1084
- method = request.request_method.downcase.to_sym
1085
- events[method].eject(&[:invoke, request]) ||
1086
- (events[:get].eject(&[:invoke, request]) if method == :head) ||
1087
- errors[NotFound].invoke(request)
1088
- end
1089
-
1090
- # Define a named template. The template may be referenced from
1091
- # event handlers by passing the name as a Symbol to rendering
1092
- # methods. The block is executed each time the template is rendered
1093
- # and the resulting object is passed to the template handler.
1094
- #
1095
- # The following example defines a HAML template named hello and
1096
- # invokes it from an event handler:
1097
- #
1098
- # template :hello do
1099
- # "h1 Hello World!"
1100
- # end
1101
- #
1102
- # get '/' do
1103
- # haml :hello
1104
- # end
1105
- #
1106
- def template(name, &b)
1107
- templates[name] = b
1108
- end
1109
-
1110
- # Define a layout template.
1111
- def layout(name=:layout, &b)
1112
- template(name, &b)
1113
- end
1114
-
1115
- # Define a custom error handler for the exception class +type+. The block
1116
- # is invoked when the specified exception type is raised from an error
1117
- # handler and can manipulate the response as needed:
1118
- #
1119
- # error MyCustomError do
1120
- # status 500
1121
- # 'So what happened was...' + request.env['sinatra.error'].message
1122
- # end
1123
- #
1124
- # The Sinatra::ServerError handler is used by default when an exception
1125
- # occurs and no matching error handler is found.
1126
- def error(type=ServerError, options = {}, &b)
1127
- errors[type] = Error.new(type, options, &b)
1128
- end
1129
-
1130
- # Define a custom error handler for '404 Not Found' responses. This is a
1131
- # shorthand for:
1132
- # error NotFound do
1133
- # ..
1134
- # end
1135
- def not_found(options={}, &b)
1136
- error NotFound, options, &b
1137
- end
1138
-
1139
- # Define a request filter. When <tt>type</tt> is <tt>:before</tt>, execute the
1140
- # block in the context of each request before matching event handlers.
1141
- def filter(type, &b)
1142
- filters[type] << b
1143
- end
1144
-
1145
- # Invoke the block in the context of each request before invoking
1146
- # matching event handlers.
1147
- def before(&b)
1148
- filter :before, &b
1149
- end
1150
-
1151
- # True when environment is :development.
1152
- def development? ; options.env == :development ; end
1153
-
1154
- # True when environment is :test.
1155
- def test? ; options.env == :test ; end
1156
-
1157
- # True when environment is :production.
1158
- def production? ; options.env == :production ; end
1159
-
1160
- # Clear all events, templates, filters, and error handlers
1161
- # and then reload the application source file. This occurs
1162
- # automatically before each request is processed in development.
1163
- def reload!
1164
- clearables.each(&:clear)
1165
- load_default_configuration!
1166
- load_development_configuration! if development?
1167
- @pipeline = nil
1168
- @reloading = true
1169
- Kernel.load options.app_file
1170
- @reloading = false
1171
- end
1172
-
1173
- # Determine whether the application is in the process of being
1174
- # reloaded.
1175
- def reloading?
1176
- @reloading == true
1177
- end
1178
-
1179
- # Mutex instance used for thread synchronization.
1180
- def mutex
1181
- @@mutex ||= Mutex.new
1182
- end
1183
-
1184
- # Yield to the block with thread synchronization
1185
- def run_safely
1186
- if development? || options.mutex
1187
- mutex.synchronize { yield }
1188
- else
1189
- yield
1190
- end
1191
- end
1192
-
1193
- # Add a piece of Rack middleware to the pipeline leading to the
1194
- # application.
1195
- def use(klass, *args, &block)
1196
- fail "#{klass} must respond to 'new'" unless klass.respond_to?(:new)
1197
- @pipeline = nil
1198
- @middleware.push([ klass, args, block ]).last
1199
- end
1200
-
1201
- private
1202
-
1203
- # Rack middleware derived from current state of application options.
1204
- # These components are plumbed in at the very beginning of the
1205
- # pipeline.
1206
- def optional_middleware
1207
- [
1208
- ([ Rack::CommonLogger, [], nil ] if options.logging),
1209
- ([ Rack::Session::Cookie, [], nil ] if options.sessions)
1210
- ].compact
1211
- end
1212
-
1213
- # Rack middleware explicitly added to the application with #use. These
1214
- # components are plumbed into the pipeline downstream from
1215
- # #optional_middle.
1216
- def explicit_middleware
1217
- @middleware
1218
- end
1219
-
1220
- # All Rack middleware used to construct the pipeline.
1221
- def middleware
1222
- optional_middleware + explicit_middleware
1223
- end
1224
-
1225
- public
1226
-
1227
- # An assembled pipeline of Rack middleware that leads eventually to
1228
- # the Application#invoke method. The pipeline is built upon first
1229
- # access. Defining new middleware with Application#use or manipulating
1230
- # application options may cause the pipeline to be rebuilt.
1231
- def pipeline
1232
- @pipeline ||=
1233
- middleware.inject(method(:dispatch)) do |app,(klass,args,block)|
1234
- klass.new(app, *args, &block)
1235
- end
1236
- end
1237
-
1238
- # Rack compatible request invocation interface.
1239
- def call(env)
1240
- run_safely do
1241
- reload! if development? && (options.reload != false)
1242
- pipeline.call(env)
1243
- end
1244
- end
1245
-
1246
- # Request invocation handler - called at the end of the Rack pipeline
1247
- # for each request.
1248
- #
1249
- # 1. Create Rack::Request, Rack::Response helper objects.
1250
- # 2. Lookup event handler based on request method and path.
1251
- # 3. Create new EventContext to house event handler evaluation.
1252
- # 4. Invoke each #before filter in context of EventContext object.
1253
- # 5. Invoke event handler in context of EventContext object.
1254
- # 6. Return response to Rack.
1255
- #
1256
- # See the Rack specification for detailed information on the
1257
- # +env+ argument and return value.
1258
- def dispatch(env)
1259
- request = Rack::Request.new(env)
1260
- context = EventContext.new(request, Rack::Response.new([], 200), {})
1261
- begin
1262
- returned =
1263
- catch(:halt) do
1264
- filters[:before].each { |f| context.instance_eval(&f) }
1265
- result = lookup(context.request)
1266
- context.route_params = result.params
1267
- context.response.status = result.status
1268
- context.reset!
1269
- [:complete, context.instance_eval(&result.block)]
1270
- end
1271
- body = returned.to_result(context)
1272
- rescue => e
1273
- msg = "#{e.class.name} - #{e.message}:"
1274
- msg << "\n #{e.backtrace.join("\n ")}"
1275
- request.env['rack.errors'] << msg
1276
-
1277
- request.env['sinatra.error'] = e
1278
- status = e.class.code rescue 500
1279
- context.status(status)
1280
- raise if options.raise_errors && e.class != NotFound
1281
- result = (errors[e.class] || errors[ServerError]).invoke(request)
1282
- returned =
1283
- catch(:halt) do
1284
- [:complete, context.instance_eval(&result.block)]
1285
- end
1286
- body = returned.to_result(context)
1287
- end
1288
- body = '' unless body.respond_to?(:each)
1289
- body = '' if request.env["REQUEST_METHOD"].upcase == 'HEAD'
1290
- context.body = body.kind_of?(String) ? [*body] : body
1291
- context.finish
1292
- end
1293
-
1294
- private
1295
-
1296
- # Called immediately after the application is initialized or reloaded to
1297
- # register default events, templates, and error handlers.
1298
- def load_default_configuration!
1299
- events[:get] << Static.new(self)
1300
- configure do
1301
- error do
1302
- '<h1>Internal Server Error</h1>'
1303
- end
1304
- not_found { '<h1>Not Found</h1>'}
1305
- end
1306
- end
1307
-
1308
- # Called before reloading to perform development specific configuration.
1309
- def load_development_configuration!
1310
- get '/sinatra_custom_images/:image.png' do
1311
- content_type :png
1312
- File.read(File.dirname(__FILE__) + "/../images/#{params[:image]}.png")
1313
- end
1314
-
1315
- not_found do
1316
- (<<-HTML).gsub(/^ {8}/, '')
1317
- <!DOCTYPE html>
1318
- <html>
1319
- <head>
1320
- <style type="text/css">
1321
- body {text-align:center;color:#888;font-family:arial;font-size:22px;margin:20px;}
1322
- #content {margin:0 auto;width:500px;text-align:left}
1323
- </style>
1324
- </head>
1325
- <body>
1326
- <h2>Sinatra doesn't know this diddy.</h2>
1327
- <img src='/sinatra_custom_images/404.png'>
1328
- <div id="content">
1329
- Try this:
1330
- <pre>#{request.request_method.downcase} "#{request.path_info}" do\n .. do something ..\nend<pre>
1331
- </div>
1332
- </body>
1333
- </html>
1334
- HTML
1335
- end
1336
-
1337
- error do
1338
- @error = request.env['sinatra.error']
1339
- (<<-HTML).gsub(/^ {8}/, '')
1340
- <!DOCTYPE html>
1341
- <html>
1342
- <head>
1343
- <style type="text/css" media="screen">
1344
- body {font-family:verdana;color:#333}
1345
- #content {width:700px;margin-left:20px}
1346
- #content h1 {width:99%;color:#1D6B8D;font-weight:bold}
1347
- #stacktrace {margin-top:-20px}
1348
- #stacktrace pre {font-size:12px;border-left:2px solid #ddd;padding-left:10px}
1349
- #stacktrace img {margin-top:10px}
1350
- </style>
1351
- </head>
1352
- <body>
1353
- <div id="content">
1354
- <img src="/sinatra_custom_images/500.png">
1355
- <div class="info">
1356
- Params: <pre>#{params.inspect}</pre>
1357
- </div>
1358
- <div id="stacktrace">
1359
- <h1>#{escape_html(@error.class.name + ' - ' + @error.message.to_s)}</h1>
1360
- <pre><code>#{escape_html(@error.backtrace.join("\n"))}</code></pre>
1361
- </div>
1362
- </div>
1363
- </body>
1364
- </html>
1365
- HTML
1366
- end
1367
- end
1368
-
1369
- end
1370
-
1371
- end
1372
-
1373
- # Delegate DSLish methods to the currently active Sinatra::Application
1374
- # instance.
1375
- Sinatra::Application::FORWARD_METHODS.each do |method|
1376
- eval(<<-EOS, binding, '(__DSL__)', 1)
1377
- def #{method}(*args, &b)
1378
- Sinatra.application.#{method}(*args, &b)
1379
- end
1380
- EOS
1381
- end
1382
-
1383
- def helpers(&b)
1384
- Sinatra::EventContext.class_eval(&b)
1385
- end
1386
-
1387
- def use_in_file_templates!
1388
- file = caller.first.sub(/:\d+$/, '')
1389
- data = IO.read(file).split('__END__').last
1390
- data.gsub! /\r\n/, "\n"
1391
- current_template = nil
1392
- data.each_line do |line|
1393
- if line =~ /^@@\s?(.*)/
1394
- current_template = $1.to_sym
1395
- Sinatra.application.templates[current_template] = ''
1396
- elsif current_template
1397
- Sinatra.application.templates[current_template] << line
1398
- end
1399
- end
1400
- end
1401
-
1402
- def mime(ext, type)
1403
- Rack::File::MIME_TYPES[ext.to_s] = type
1404
- end
1405
-
1406
- ### Misc Core Extensions
1407
-
1408
- module Kernel
1409
- def silence_warnings
1410
- old_verbose, $VERBOSE = $VERBOSE, nil
1411
- yield
1412
- ensure
1413
- $VERBOSE = old_verbose
1414
- end
1415
- end
1416
-
1417
- class Symbol
1418
- def to_proc
1419
- Proc.new { |*args| args.shift.__send__(self, *args) }
1420
- end
1421
- end
1422
-
1423
- class Array
1424
- def to_hash
1425
- self.inject({}) { |h, (k, v)| h[k] = v; h }
1426
- end
1427
- def to_proc
1428
- Proc.new { |*args| args.shift.__send__(self[0], *(args + self[1..-1])) }
1429
- end
1430
- end
1431
-
1432
- module Enumerable
1433
- def eject(&block)
1434
- find { |e| result = block[e] and break result }
1435
- end
1436
- end
1437
-
1438
- ### Core Extension results for throw :halt
1439
-
1440
- class Proc
1441
- def to_result(cx, *args)
1442
- cx.instance_eval(&self)
1443
- args.shift.to_result(cx, *args)
1444
- end
1445
- end
1446
-
1447
- class String
1448
- def to_result(cx, *args)
1449
- args.shift.to_result(cx, *args)
1450
- self
1451
- end
1452
- end
1453
-
1454
- class Array
1455
- def to_result(cx, *args)
1456
- self.shift.to_result(cx, *self)
1457
- end
1458
- end
1459
-
1460
- class Symbol
1461
- def to_result(cx, *args)
1462
- cx.send(self, *args)
1463
- end
1464
- end
1465
-
1466
- class Fixnum
1467
- def to_result(cx, *args)
1468
- cx.status self
1469
- args.shift.to_result(cx, *args)
1470
- end
1471
- end
1472
-
1473
- class NilClass
1474
- def to_result(cx, *args)
1475
- ''
1476
- end
1477
- end
1478
-
1479
- at_exit do
1480
- raise $! if $!
1481
- Sinatra.run if Sinatra.application.options.run
1482
- end
1483
-
1484
- mime :xml, 'application/xml'
1485
- mime :js, 'application/javascript'
1486
- mime :png, 'image/png'
8
+ use_in_file_templates!