rtomayko-sinatra 0.3.0

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