Syd-sinatra 0.3.2 → 0.9.0.2

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