Syd-sinatra 0.3.2

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 (48) hide show
  1. data/ChangeLog +78 -0
  2. data/LICENSE +22 -0
  3. data/README.rdoc +523 -0
  4. data/Rakefile +162 -0
  5. data/images/404.png +0 -0
  6. data/images/500.png +0 -0
  7. data/lib/sinatra/test/methods.rb +76 -0
  8. data/lib/sinatra/test/rspec.rb +10 -0
  9. data/lib/sinatra/test/spec.rb +10 -0
  10. data/lib/sinatra/test/unit.rb +13 -0
  11. data/lib/sinatra.rb +1470 -0
  12. data/sinatra.gemspec +77 -0
  13. data/test/app_test.rb +299 -0
  14. data/test/application_test.rb +318 -0
  15. data/test/builder_test.rb +101 -0
  16. data/test/custom_error_test.rb +62 -0
  17. data/test/erb_test.rb +136 -0
  18. data/test/event_context_test.rb +15 -0
  19. data/test/events_test.rb +65 -0
  20. data/test/filter_test.rb +30 -0
  21. data/test/haml_test.rb +233 -0
  22. data/test/helper.rb +7 -0
  23. data/test/mapped_error_test.rb +72 -0
  24. data/test/pipeline_test.rb +66 -0
  25. data/test/public/foo.xml +1 -0
  26. data/test/sass_test.rb +57 -0
  27. data/test/sessions_test.rb +39 -0
  28. data/test/streaming_test.rb +118 -0
  29. data/test/sym_params_test.rb +19 -0
  30. data/test/template_test.rb +30 -0
  31. data/test/use_in_file_templates_test.rb +47 -0
  32. data/test/views/foo.builder +1 -0
  33. data/test/views/foo.erb +1 -0
  34. data/test/views/foo.haml +1 -0
  35. data/test/views/foo.sass +2 -0
  36. data/test/views/foo_layout.erb +2 -0
  37. data/test/views/foo_layout.haml +2 -0
  38. data/test/views/layout_test/foo.builder +1 -0
  39. data/test/views/layout_test/foo.erb +1 -0
  40. data/test/views/layout_test/foo.haml +1 -0
  41. data/test/views/layout_test/foo.sass +2 -0
  42. data/test/views/layout_test/layout.builder +3 -0
  43. data/test/views/layout_test/layout.erb +1 -0
  44. data/test/views/layout_test/layout.haml +1 -0
  45. data/test/views/layout_test/layout.sass +2 -0
  46. data/test/views/no_layout/no_layout.builder +1 -0
  47. data/test/views/no_layout/no_layout.haml +1 -0
  48. metadata +129 -0
data/lib/sinatra.rb ADDED
@@ -0,0 +1,1470 @@
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
+ 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'