sinatra 2.2.4 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sinatra might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +61 -21
- data/Gemfile +41 -65
- data/README.md +102 -399
- data/Rakefile +67 -67
- data/VERSION +1 -1
- data/examples/chat.rb +5 -3
- data/examples/rainbows.rb +3 -1
- data/examples/simple.rb +2 -0
- data/examples/stream.ru +2 -0
- data/lib/sinatra/base.rb +330 -339
- data/lib/sinatra/indifferent_hash.rb +25 -33
- data/lib/sinatra/main.rb +18 -16
- data/lib/sinatra/show_exceptions.rb +17 -15
- data/lib/sinatra/version.rb +3 -1
- data/lib/sinatra.rb +2 -0
- data/sinatra.gemspec +38 -33
- metadata +33 -25
- data/README.de.md +0 -3239
- data/README.es.md +0 -3231
- data/README.fr.md +0 -3111
- data/README.hu.md +0 -728
- data/README.ja.md +0 -2844
- data/README.ko.md +0 -2967
- data/README.malayalam.md +0 -3141
- data/README.pt-br.md +0 -3787
- data/README.pt-pt.md +0 -791
- data/README.ru.md +0 -3207
- data/README.zh.md +0 -2934
data/lib/sinatra/base.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
# external dependencies
|
@@ -10,7 +9,6 @@ require 'mustermann/sinatra'
|
|
10
9
|
require 'mustermann/regular'
|
11
10
|
|
12
11
|
# stdlib dependencies
|
13
|
-
require 'thread'
|
14
12
|
require 'time'
|
15
13
|
require 'uri'
|
16
14
|
|
@@ -23,19 +21,20 @@ module Sinatra
|
|
23
21
|
# The request object. See Rack::Request for more info:
|
24
22
|
# http://rubydoc.info/github/rack/rack/master/Rack/Request
|
25
23
|
class Request < Rack::Request
|
26
|
-
HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s
|
27
|
-
HEADER_VALUE_WITH_PARAMS =
|
24
|
+
HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/.freeze
|
25
|
+
HEADER_VALUE_WITH_PARAMS = %r{(?:(?:\w+|\*)/(?:\w+(?:\.|-|\+)?|\*)*)\s*(?:;#{HEADER_PARAM})*}.freeze
|
28
26
|
|
29
27
|
# Returns an array of acceptable media types for the response
|
30
28
|
def accept
|
31
|
-
@env['sinatra.accept'] ||=
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
29
|
+
@env['sinatra.accept'] ||= if @env.include?('HTTP_ACCEPT') && (@env['HTTP_ACCEPT'].to_s != '')
|
30
|
+
@env['HTTP_ACCEPT']
|
31
|
+
.to_s
|
32
|
+
.scan(HEADER_VALUE_WITH_PARAMS)
|
33
|
+
.map! { |e| AcceptEntry.new(e) }
|
34
|
+
.sort
|
35
|
+
else
|
36
|
+
[AcceptEntry.new('*/*')]
|
37
|
+
end
|
39
38
|
end
|
40
39
|
|
41
40
|
def accept?(type)
|
@@ -44,8 +43,10 @@ module Sinatra
|
|
44
43
|
|
45
44
|
def preferred_type(*types)
|
46
45
|
return accept.first if types.empty?
|
46
|
+
|
47
47
|
types.flatten!
|
48
48
|
return types.first if accept.empty?
|
49
|
+
|
49
50
|
accept.detect do |accept_header|
|
50
51
|
type = types.detect { |t| MimeTypeEntry.new(t).accepts?(accept_header) }
|
51
52
|
return type if type
|
@@ -55,23 +56,23 @@ module Sinatra
|
|
55
56
|
alias secure? ssl?
|
56
57
|
|
57
58
|
def forwarded?
|
58
|
-
@env.include?
|
59
|
+
@env.include? 'HTTP_X_FORWARDED_HOST'
|
59
60
|
end
|
60
61
|
|
61
62
|
def safe?
|
62
|
-
get?
|
63
|
+
get? || head? || options? || trace?
|
63
64
|
end
|
64
65
|
|
65
66
|
def idempotent?
|
66
|
-
safe?
|
67
|
+
safe? || put? || delete? || link? || unlink?
|
67
68
|
end
|
68
69
|
|
69
70
|
def link?
|
70
|
-
request_method ==
|
71
|
+
request_method == 'LINK'
|
71
72
|
end
|
72
73
|
|
73
74
|
def unlink?
|
74
|
-
request_method ==
|
75
|
+
request_method == 'UNLINK'
|
75
76
|
end
|
76
77
|
|
77
78
|
def params
|
@@ -95,17 +96,17 @@ module Sinatra
|
|
95
96
|
|
96
97
|
@entry = entry
|
97
98
|
@type = entry[/[^;]+/].delete(' ')
|
98
|
-
@params =
|
99
|
+
@params = params.to_h
|
99
100
|
@q = @params.delete('q') { 1.0 }.to_f
|
100
101
|
end
|
101
102
|
|
102
103
|
def <=>(other)
|
103
|
-
other.priority <=>
|
104
|
+
other.priority <=> priority
|
104
105
|
end
|
105
106
|
|
106
107
|
def priority
|
107
108
|
# We sort in descending order; better matches should be higher.
|
108
|
-
[
|
109
|
+
[@q, -@type.count('*'), @params.size]
|
109
110
|
end
|
110
111
|
|
111
112
|
def to_str
|
@@ -117,7 +118,7 @@ module Sinatra
|
|
117
118
|
end
|
118
119
|
|
119
120
|
def respond_to?(*args)
|
120
|
-
super
|
121
|
+
super || to_str.respond_to?(*args)
|
121
122
|
end
|
122
123
|
|
123
124
|
def method_missing(*args, &block)
|
@@ -136,7 +137,7 @@ module Sinatra
|
|
136
137
|
end
|
137
138
|
|
138
139
|
@type = entry[/[^;]+/].delete(' ')
|
139
|
-
@params =
|
140
|
+
@params = params.to_h
|
140
141
|
end
|
141
142
|
|
142
143
|
def accepts?(entry)
|
@@ -150,7 +151,7 @@ module Sinatra
|
|
150
151
|
def matches_params?(params)
|
151
152
|
return true if @params.empty?
|
152
153
|
|
153
|
-
params.all? { |k,v| !@params.
|
154
|
+
params.all? { |k, v| !@params.key?(k) || @params[k] == v }
|
154
155
|
end
|
155
156
|
end
|
156
157
|
end
|
@@ -160,7 +161,7 @@ module Sinatra
|
|
160
161
|
# http://rubydoc.info/github/rack/rack/master/Rack/Response
|
161
162
|
# http://rubydoc.info/github/rack/rack/master/Rack/Response/Helpers
|
162
163
|
class Response < Rack::Response
|
163
|
-
DROP_BODY_RESPONSES = [204, 304]
|
164
|
+
DROP_BODY_RESPONSES = [204, 304].freeze
|
164
165
|
|
165
166
|
def body=(value)
|
166
167
|
value = value.body while Rack::Response === value
|
@@ -175,8 +176,8 @@ module Sinatra
|
|
175
176
|
result = body
|
176
177
|
|
177
178
|
if drop_content_info?
|
178
|
-
headers.delete
|
179
|
-
headers.delete
|
179
|
+
headers.delete 'Content-Length'
|
180
|
+
headers.delete 'Content-Type'
|
180
181
|
end
|
181
182
|
|
182
183
|
if drop_body?
|
@@ -187,7 +188,7 @@ module Sinatra
|
|
187
188
|
if calculate_content_length?
|
188
189
|
# if some other code has already set Content-Length, don't muck with it
|
189
190
|
# currently, this would be the static file-handler
|
190
|
-
headers[
|
191
|
+
headers['Content-Length'] = body.map(&:bytesize).reduce(0, :+).to_s
|
191
192
|
end
|
192
193
|
|
193
194
|
[status, headers, result]
|
@@ -196,11 +197,11 @@ module Sinatra
|
|
196
197
|
private
|
197
198
|
|
198
199
|
def calculate_content_length?
|
199
|
-
headers[
|
200
|
+
headers['Content-Type'] && !headers['Content-Length'] && (Array === body)
|
200
201
|
end
|
201
202
|
|
202
203
|
def drop_content_info?
|
203
|
-
informational?
|
204
|
+
informational? || drop_body?
|
204
205
|
end
|
205
206
|
|
206
207
|
def drop_body?
|
@@ -215,8 +216,10 @@ module Sinatra
|
|
215
216
|
# still be able to run.
|
216
217
|
class ExtendedRack < Struct.new(:app)
|
217
218
|
def call(env)
|
218
|
-
result
|
219
|
-
|
219
|
+
result = app.call(env)
|
220
|
+
callback = env['async.callback']
|
221
|
+
return result unless callback && async?(*result)
|
222
|
+
|
220
223
|
after_response { callback.call result }
|
221
224
|
setup_close(env, *result)
|
222
225
|
throw :async
|
@@ -224,20 +227,23 @@ module Sinatra
|
|
224
227
|
|
225
228
|
private
|
226
229
|
|
227
|
-
def setup_close(env,
|
228
|
-
return unless body.respond_to?
|
230
|
+
def setup_close(env, _status, _headers, body)
|
231
|
+
return unless body.respond_to?(:close) && env.include?('async.close')
|
232
|
+
|
229
233
|
env['async.close'].callback { body.close }
|
230
234
|
env['async.close'].errback { body.close }
|
231
235
|
end
|
232
236
|
|
233
237
|
def after_response(&block)
|
234
|
-
raise NotImplementedError,
|
238
|
+
raise NotImplementedError, 'only supports EventMachine at the moment' unless defined? EventMachine
|
239
|
+
|
235
240
|
EventMachine.next_tick(&block)
|
236
241
|
end
|
237
242
|
|
238
|
-
def async?(status,
|
243
|
+
def async?(status, _headers, body)
|
239
244
|
return true if status == -1
|
240
|
-
|
245
|
+
|
246
|
+
body.respond_to?(:callback) && body.respond_to?(:errback)
|
241
247
|
end
|
242
248
|
end
|
243
249
|
|
@@ -249,7 +255,7 @@ module Sinatra
|
|
249
255
|
end
|
250
256
|
|
251
257
|
superclass.class_eval do
|
252
|
-
|
258
|
+
alias_method :call_without_check, :call unless method_defined? :call_without_check
|
253
259
|
def call(env)
|
254
260
|
env['sinatra.commonlogger'] = true
|
255
261
|
call_without_check(env)
|
@@ -257,11 +263,14 @@ module Sinatra
|
|
257
263
|
end
|
258
264
|
end
|
259
265
|
|
260
|
-
class
|
266
|
+
class Error < StandardError # :nodoc:
|
267
|
+
end
|
268
|
+
|
269
|
+
class BadRequest < Error # :nodoc:
|
261
270
|
def http_status; 400 end
|
262
271
|
end
|
263
272
|
|
264
|
-
class NotFound <
|
273
|
+
class NotFound < Error # :nodoc:
|
265
274
|
def http_status; 404 end
|
266
275
|
end
|
267
276
|
|
@@ -293,7 +302,7 @@ module Sinatra
|
|
293
302
|
|
294
303
|
# Halt processing and redirect to the URI provided.
|
295
304
|
def redirect(uri, *args)
|
296
|
-
if env['HTTP_VERSION'] == 'HTTP/1.1'
|
305
|
+
if (env['HTTP_VERSION'] == 'HTTP/1.1') && (env['REQUEST_METHOD'] != 'GET')
|
297
306
|
status 303
|
298
307
|
else
|
299
308
|
status 302
|
@@ -308,18 +317,19 @@ module Sinatra
|
|
308
317
|
# Generates the absolute URI for a given path in the app.
|
309
318
|
# Takes Rack routers and reverse proxies into account.
|
310
319
|
def uri(addr = nil, absolute = true, add_script_name = true)
|
311
|
-
return addr if addr =~ /\A[a-z][a-z0-9
|
320
|
+
return addr if addr =~ /\A[a-z][a-z0-9+.\-]*:/i
|
321
|
+
|
312
322
|
uri = [host = String.new]
|
313
323
|
if absolute
|
314
324
|
host << "http#{'s' if request.secure?}://"
|
315
|
-
if request.forwarded?
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
325
|
+
host << if request.forwarded? || (request.port != (request.secure? ? 443 : 80))
|
326
|
+
request.host_with_port
|
327
|
+
else
|
328
|
+
request.host
|
329
|
+
end
|
320
330
|
end
|
321
331
|
uri << request.script_name.to_s if add_script_name
|
322
|
-
uri << (addr
|
332
|
+
uri << (addr || request.path_info).to_s
|
323
333
|
File.join uri
|
324
334
|
end
|
325
335
|
|
@@ -328,7 +338,10 @@ module Sinatra
|
|
328
338
|
|
329
339
|
# Halt processing and return the error status provided.
|
330
340
|
def error(code, body = nil)
|
331
|
-
|
341
|
+
if code.respond_to? :to_str
|
342
|
+
body = code.to_str
|
343
|
+
code = 500
|
344
|
+
end
|
332
345
|
response.body = body unless body.nil?
|
333
346
|
halt code
|
334
347
|
end
|
@@ -363,11 +376,13 @@ module Sinatra
|
|
363
376
|
# extension.
|
364
377
|
def content_type(type = nil, params = {})
|
365
378
|
return response['Content-Type'] unless type
|
379
|
+
|
366
380
|
default = params.delete :default
|
367
381
|
mime_type = mime_type(type) || default
|
368
|
-
|
382
|
+
raise format('Unknown media type: %p', type) if mime_type.nil?
|
383
|
+
|
369
384
|
mime_type = mime_type.dup
|
370
|
-
unless params.include?
|
385
|
+
unless params.include?(:charset) || settings.add_charset.all? { |p| !(p === mime_type) }
|
371
386
|
params[:charset] = params.delete('charset') || settings.default_encoding
|
372
387
|
end
|
373
388
|
params.delete :charset if mime_type.include? 'charset'
|
@@ -381,20 +396,13 @@ module Sinatra
|
|
381
396
|
response['Content-Type'] = mime_type
|
382
397
|
end
|
383
398
|
|
384
|
-
# https://html.spec.whatwg.org/#multipart-form-data
|
385
|
-
MULTIPART_FORM_DATA_REPLACEMENT_TABLE = {
|
386
|
-
'"' => '%22',
|
387
|
-
"\r" => '%0D',
|
388
|
-
"\n" => '%0A'
|
389
|
-
}.freeze
|
390
|
-
|
391
399
|
# Set the Content-Disposition to "attachment" with the specified filename,
|
392
400
|
# instructing the user agents to prompt to save.
|
393
401
|
def attachment(filename = nil, disposition = :attachment)
|
394
402
|
response['Content-Disposition'] = disposition.to_s.dup
|
395
403
|
return unless filename
|
396
404
|
|
397
|
-
params = format('; filename="%s"', File.basename(filename)
|
405
|
+
params = format('; filename="%s"', File.basename(filename))
|
398
406
|
response['Content-Disposition'] << params
|
399
407
|
ext = File.extname(filename)
|
400
408
|
content_type(ext) unless response['Content-Type'] || ext.empty?
|
@@ -402,13 +410,13 @@ module Sinatra
|
|
402
410
|
|
403
411
|
# Use the contents of the file at +path+ as the response body.
|
404
412
|
def send_file(path, opts = {})
|
405
|
-
if opts[:type]
|
406
|
-
content_type opts[:type] || File.extname(path), :
|
413
|
+
if opts[:type] || !response['Content-Type']
|
414
|
+
content_type opts[:type] || File.extname(path), default: 'application/octet-stream'
|
407
415
|
end
|
408
416
|
|
409
417
|
disposition = opts[:disposition]
|
410
418
|
filename = opts[:filename]
|
411
|
-
disposition = :attachment if disposition.nil?
|
419
|
+
disposition = :attachment if disposition.nil? && filename
|
412
420
|
filename = path if filename.nil?
|
413
421
|
attachment(filename, disposition) if disposition
|
414
422
|
|
@@ -417,7 +425,7 @@ module Sinatra
|
|
417
425
|
file = Rack::File.new(File.dirname(settings.app_file))
|
418
426
|
result = file.serving(request, path)
|
419
427
|
|
420
|
-
result[1].each { |k,v| headers[k] ||= v }
|
428
|
+
result[1].each { |k, v| headers[k] ||= v }
|
421
429
|
headers['Content-Length'] = result[1]['Content-Length']
|
422
430
|
opts[:status] &&= Integer(opts[:status])
|
423
431
|
halt (opts[:status] || result[0]), result[2]
|
@@ -438,12 +446,16 @@ module Sinatra
|
|
438
446
|
def self.defer(*) yield end
|
439
447
|
|
440
448
|
def initialize(scheduler = self.class, keep_open = false, &back)
|
441
|
-
@back
|
442
|
-
@
|
449
|
+
@back = back.to_proc
|
450
|
+
@scheduler = scheduler
|
451
|
+
@keep_open = keep_open
|
452
|
+
@callbacks = []
|
453
|
+
@closed = false
|
443
454
|
end
|
444
455
|
|
445
456
|
def close
|
446
457
|
return if closed?
|
458
|
+
|
447
459
|
@closed = true
|
448
460
|
@scheduler.schedule { @callbacks.each { |c| c.call } }
|
449
461
|
end
|
@@ -467,6 +479,7 @@ module Sinatra
|
|
467
479
|
|
468
480
|
def callback(&block)
|
469
481
|
return yield if closed?
|
482
|
+
|
470
483
|
@callbacks << block
|
471
484
|
end
|
472
485
|
|
@@ -500,18 +513,18 @@ module Sinatra
|
|
500
513
|
# See RFC 2616 / 14.9 for more on standard cache control directives:
|
501
514
|
# http://tools.ietf.org/html/rfc2616#section-14.9.1
|
502
515
|
def cache_control(*values)
|
503
|
-
if values.last.
|
516
|
+
if values.last.is_a?(Hash)
|
504
517
|
hash = values.pop
|
505
|
-
hash.reject! { |
|
518
|
+
hash.reject! { |_k, v| v == false }
|
506
519
|
hash.reject! { |k, v| values << k if v == true }
|
507
520
|
else
|
508
521
|
hash = {}
|
509
522
|
end
|
510
523
|
|
511
|
-
values.map! { |value| value.to_s.tr('_','-') }
|
524
|
+
values.map! { |value| value.to_s.tr('_', '-') }
|
512
525
|
hash.each do |key, value|
|
513
526
|
key = key.to_s.tr('_', '-')
|
514
|
-
value = value.to_i if [
|
527
|
+
value = value.to_i if %w[max-age s-maxage].include? key
|
515
528
|
values << "#{key}=#{value}"
|
516
529
|
end
|
517
530
|
|
@@ -528,7 +541,7 @@ module Sinatra
|
|
528
541
|
# => Expires: Mon, 08 Jun 2009 08:50:17 GMT
|
529
542
|
#
|
530
543
|
def expires(amount, *values)
|
531
|
-
values << {} unless values.last.
|
544
|
+
values << {} unless values.last.is_a?(Hash)
|
532
545
|
|
533
546
|
if amount.is_a? Integer
|
534
547
|
time = Time.now + amount.to_i
|
@@ -538,7 +551,7 @@ module Sinatra
|
|
538
551
|
max_age = time - Time.now
|
539
552
|
end
|
540
553
|
|
541
|
-
values.last.merge!(:max_age
|
554
|
+
values.last.merge!(max_age: max_age) { |_key, v1, v2| v1 || v2 }
|
542
555
|
cache_control(*values)
|
543
556
|
|
544
557
|
response['Expires'] = time.httpdate
|
@@ -553,17 +566,18 @@ module Sinatra
|
|
553
566
|
# with a '304 Not Modified' response.
|
554
567
|
def last_modified(time)
|
555
568
|
return unless time
|
569
|
+
|
556
570
|
time = time_for time
|
557
571
|
response['Last-Modified'] = time.httpdate
|
558
572
|
return if env['HTTP_IF_NONE_MATCH']
|
559
573
|
|
560
|
-
if status == 200
|
574
|
+
if (status == 200) && env['HTTP_IF_MODIFIED_SINCE']
|
561
575
|
# compare based on seconds since epoch
|
562
576
|
since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
|
563
577
|
halt 304 if since >= time.to_i
|
564
578
|
end
|
565
579
|
|
566
|
-
if (success?
|
580
|
+
if (success? || (status == 412)) && env['HTTP_IF_UNMODIFIED_SINCE']
|
567
581
|
# compare based on seconds since epoch
|
568
582
|
since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
|
569
583
|
halt 412 if since < time.to_i
|
@@ -571,7 +585,7 @@ module Sinatra
|
|
571
585
|
rescue ArgumentError
|
572
586
|
end
|
573
587
|
|
574
|
-
ETAG_KINDS = [
|
588
|
+
ETAG_KINDS = %i[strong weak].freeze
|
575
589
|
# Set the response entity tag (HTTP 'ETag' header) and halt if conditional
|
576
590
|
# GET matches. The +value+ argument is an identifier that uniquely
|
577
591
|
# identifies the current version of the resource. The +kind+ argument
|
@@ -583,27 +597,31 @@ module Sinatra
|
|
583
597
|
# GET or HEAD, a '304 Not Modified' response is sent.
|
584
598
|
def etag(value, options = {})
|
585
599
|
# Before touching this code, please double check RFC 2616 14.24 and 14.26.
|
586
|
-
options = {:
|
600
|
+
options = { kind: options } unless Hash === options
|
587
601
|
kind = options[:kind] || :strong
|
588
602
|
new_resource = options.fetch(:new_resource) { request.post? }
|
589
603
|
|
590
604
|
unless ETAG_KINDS.include?(kind)
|
591
|
-
raise ArgumentError,
|
605
|
+
raise ArgumentError, ':strong or :weak expected'
|
592
606
|
end
|
593
607
|
|
594
|
-
value = '"%s"'
|
608
|
+
value = format('"%s"', value)
|
595
609
|
value = "W/#{value}" if kind == :weak
|
596
610
|
response['ETag'] = value
|
597
611
|
|
598
|
-
|
599
|
-
if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
|
600
|
-
halt(request.safe? ? 304 : 412)
|
601
|
-
end
|
612
|
+
return unless success? || status == 304
|
602
613
|
|
603
|
-
|
604
|
-
|
605
|
-
end
|
614
|
+
if etag_matches?(env['HTTP_IF_NONE_MATCH'], new_resource)
|
615
|
+
halt(request.safe? ? 304 : 412)
|
606
616
|
end
|
617
|
+
|
618
|
+
if env['HTTP_IF_MATCH']
|
619
|
+
return if etag_matches?(env['HTTP_IF_MATCH'], new_resource)
|
620
|
+
|
621
|
+
halt 412
|
622
|
+
end
|
623
|
+
|
624
|
+
nil
|
607
625
|
end
|
608
626
|
|
609
627
|
# Sugar for redirect (example: redirect back)
|
@@ -656,8 +674,8 @@ module Sinatra
|
|
656
674
|
else
|
657
675
|
value.to_time
|
658
676
|
end
|
659
|
-
rescue ArgumentError =>
|
660
|
-
raise
|
677
|
+
rescue ArgumentError => e
|
678
|
+
raise e
|
661
679
|
rescue Exception
|
662
680
|
raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
|
663
681
|
end
|
@@ -667,11 +685,13 @@ module Sinatra
|
|
667
685
|
# Helper method checking if a ETag value list includes the current ETag.
|
668
686
|
def etag_matches?(list, new_resource = request.post?)
|
669
687
|
return !new_resource if list == '*'
|
688
|
+
|
670
689
|
list.to_s.split(/\s*,\s*/).include? response['ETag']
|
671
690
|
end
|
672
691
|
|
673
692
|
def with_params(temp_params)
|
674
|
-
original
|
693
|
+
original = @params
|
694
|
+
@params = temp_params
|
675
695
|
yield
|
676
696
|
ensure
|
677
697
|
@params = original if original
|
@@ -689,7 +709,7 @@ module Sinatra
|
|
689
709
|
# Possible options are:
|
690
710
|
# :content_type The content type to use, same arguments as content_type.
|
691
711
|
# :layout If set to something falsy, no layout is rendered, otherwise
|
692
|
-
# the specified layout is used
|
712
|
+
# the specified layout is used
|
693
713
|
# :layout_engine Engine to use for rendering the layout.
|
694
714
|
# :locals A hash with local variables that should be available
|
695
715
|
# in the template
|
@@ -711,36 +731,10 @@ module Sinatra
|
|
711
731
|
render(:erb, template, options, locals, &block)
|
712
732
|
end
|
713
733
|
|
714
|
-
def erubis(template, options = {}, locals = {})
|
715
|
-
warn "Sinatra::Templates#erubis is deprecated and will be removed, use #erb instead.\n" \
|
716
|
-
"If you have Erubis installed, it will be used automatically."
|
717
|
-
render :erubis, template, options, locals
|
718
|
-
end
|
719
|
-
|
720
734
|
def haml(template, options = {}, locals = {}, &block)
|
721
735
|
render(:haml, template, options, locals, &block)
|
722
736
|
end
|
723
737
|
|
724
|
-
def sass(template, options = {}, locals = {})
|
725
|
-
options.merge! :layout => false, :default_content_type => :css
|
726
|
-
render :sass, template, options, locals
|
727
|
-
end
|
728
|
-
|
729
|
-
def scss(template, options = {}, locals = {})
|
730
|
-
options.merge! :layout => false, :default_content_type => :css
|
731
|
-
render :scss, template, options, locals
|
732
|
-
end
|
733
|
-
|
734
|
-
def less(template, options = {}, locals = {})
|
735
|
-
options.merge! :layout => false, :default_content_type => :css
|
736
|
-
render :less, template, options, locals
|
737
|
-
end
|
738
|
-
|
739
|
-
def stylus(template, options = {}, locals = {})
|
740
|
-
options.merge! :layout => false, :default_content_type => :css
|
741
|
-
render :styl, template, options, locals
|
742
|
-
end
|
743
|
-
|
744
738
|
def builder(template = nil, options = {}, locals = {}, &block)
|
745
739
|
options[:default_content_type] = :xml
|
746
740
|
render_ruby(:builder, template, options, locals, &block)
|
@@ -755,10 +749,6 @@ module Sinatra
|
|
755
749
|
render :markdown, template, options, locals
|
756
750
|
end
|
757
751
|
|
758
|
-
def textile(template, options = {}, locals = {})
|
759
|
-
render :textile, template, options, locals
|
760
|
-
end
|
761
|
-
|
762
752
|
def rdoc(template, options = {}, locals = {})
|
763
753
|
render :rdoc, template, options, locals
|
764
754
|
end
|
@@ -767,19 +757,10 @@ module Sinatra
|
|
767
757
|
render :asciidoc, template, options, locals
|
768
758
|
end
|
769
759
|
|
770
|
-
def radius(template, options = {}, locals = {})
|
771
|
-
render :radius, template, options, locals
|
772
|
-
end
|
773
|
-
|
774
760
|
def markaby(template = nil, options = {}, locals = {}, &block)
|
775
761
|
render_ruby(:mab, template, options, locals, &block)
|
776
762
|
end
|
777
763
|
|
778
|
-
def coffee(template, options = {}, locals = {})
|
779
|
-
options.merge! :layout => false, :default_content_type => :js
|
780
|
-
render :coffee, template, options, locals
|
781
|
-
end
|
782
|
-
|
783
764
|
def nokogiri(template = nil, options = {}, locals = {}, &block)
|
784
765
|
options[:default_content_type] = :xml
|
785
766
|
render_ruby(:nokogiri, template, options, locals, &block)
|
@@ -789,18 +770,6 @@ module Sinatra
|
|
789
770
|
render(:slim, template, options, locals, &block)
|
790
771
|
end
|
791
772
|
|
792
|
-
def creole(template, options = {}, locals = {})
|
793
|
-
render :creole, template, options, locals
|
794
|
-
end
|
795
|
-
|
796
|
-
def mediawiki(template, options = {}, locals = {})
|
797
|
-
render :mediawiki, template, options, locals
|
798
|
-
end
|
799
|
-
|
800
|
-
def wlang(template, options = {}, locals = {}, &block)
|
801
|
-
render(:wlang, template, options, locals, &block)
|
802
|
-
end
|
803
|
-
|
804
773
|
def yajl(template, options = {}, locals = {})
|
805
774
|
options[:default_content_type] = :json
|
806
775
|
render :yajl, template, options, locals
|
@@ -825,24 +794,27 @@ module Sinatra
|
|
825
794
|
|
826
795
|
# logic shared between builder and nokogiri
|
827
796
|
def render_ruby(engine, template, options = {}, locals = {}, &block)
|
828
|
-
|
829
|
-
|
797
|
+
if template.is_a?(Hash)
|
798
|
+
options = template
|
799
|
+
template = nil
|
800
|
+
end
|
801
|
+
template = proc { block } if template.nil?
|
830
802
|
render engine, template, options, locals
|
831
803
|
end
|
832
804
|
|
833
805
|
def render(engine, data, options = {}, locals = {}, &block)
|
834
806
|
# merge app-level options
|
835
807
|
engine_options = settings.respond_to?(engine) ? settings.send(engine) : {}
|
836
|
-
options.merge!(engine_options) { |
|
808
|
+
options.merge!(engine_options) { |_key, v1, _v2| v1 }
|
837
809
|
|
838
810
|
# extract generic options
|
839
811
|
locals = options.delete(:locals) || locals || {}
|
840
|
-
views = options.delete(:views) || settings.views ||
|
812
|
+
views = options.delete(:views) || settings.views || './views'
|
841
813
|
layout = options[:layout]
|
842
814
|
layout = false if layout.nil? && options.include?(:layout)
|
843
815
|
eat_errors = layout.nil?
|
844
|
-
layout = engine_options[:layout] if layout.nil?
|
845
|
-
layout = @default_layout if layout.nil?
|
816
|
+
layout = engine_options[:layout] if layout.nil? || (layout == true && engine_options[:layout] != false)
|
817
|
+
layout = @default_layout if layout.nil? || (layout == true)
|
846
818
|
layout_options = options.delete(:layout_options) || {}
|
847
819
|
content_type = options.delete(:default_content_type)
|
848
820
|
content_type = options.delete(:content_type) || content_type
|
@@ -867,8 +839,9 @@ module Sinatra
|
|
867
839
|
|
868
840
|
# render layout
|
869
841
|
if layout
|
870
|
-
|
871
|
-
|
842
|
+
extra_options = { views: views, layout: false, eat_errors: eat_errors, scope: scope }
|
843
|
+
options = options.merge(extra_options).merge!(layout_options)
|
844
|
+
|
872
845
|
catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } }
|
873
846
|
end
|
874
847
|
|
@@ -893,12 +866,13 @@ module Sinatra
|
|
893
866
|
@preferred_extension = engine.to_s
|
894
867
|
find_template(views, data, template) do |file|
|
895
868
|
path ||= file # keep the initial path rather than the last one
|
896
|
-
|
869
|
+
found = File.exist?(file)
|
870
|
+
if found
|
897
871
|
path = file
|
898
872
|
break
|
899
873
|
end
|
900
874
|
end
|
901
|
-
throw :layout_missing if eat_errors
|
875
|
+
throw :layout_missing if eat_errors && !found
|
902
876
|
template.new(path, 1, options)
|
903
877
|
end
|
904
878
|
end
|
@@ -914,9 +888,11 @@ module Sinatra
|
|
914
888
|
end
|
915
889
|
|
916
890
|
def compile_block_template(template, options, &body)
|
917
|
-
|
918
|
-
path =
|
919
|
-
line =
|
891
|
+
first_location = caller_locations.first
|
892
|
+
path = first_location.path
|
893
|
+
line = first_location.lineno
|
894
|
+
path = options[:path] || path
|
895
|
+
line = options[:line] || line
|
920
896
|
template.new(path, line.to_i, options, &body)
|
921
897
|
end
|
922
898
|
end
|
@@ -932,7 +908,7 @@ module Sinatra
|
|
932
908
|
attr_accessor :app, :env, :request, :response, :params
|
933
909
|
attr_reader :template_cache
|
934
910
|
|
935
|
-
def initialize(app = nil, **
|
911
|
+
def initialize(app = nil, **_kwargs)
|
936
912
|
super()
|
937
913
|
@app = app
|
938
914
|
@template_cache = Tilt::Cache.new
|
@@ -959,7 +935,7 @@ module Sinatra
|
|
959
935
|
unless @response['Content-Type']
|
960
936
|
if Array === body && body[0].respond_to?(:content_type)
|
961
937
|
content_type body[0].content_type
|
962
|
-
elsif default = settings.default_content_type
|
938
|
+
elsif (default = settings.default_content_type)
|
963
939
|
content_type default
|
964
940
|
end
|
965
941
|
end
|
@@ -977,12 +953,6 @@ module Sinatra
|
|
977
953
|
self.class.settings
|
978
954
|
end
|
979
955
|
|
980
|
-
def options
|
981
|
-
warn "Sinatra::Base#options is deprecated and will be removed, " \
|
982
|
-
"use #settings instead."
|
983
|
-
settings
|
984
|
-
end
|
985
|
-
|
986
956
|
# Exit the current block, halts any further processing
|
987
957
|
# of the request, and returns the specified response.
|
988
958
|
def halt(*response)
|
@@ -999,7 +969,8 @@ module Sinatra
|
|
999
969
|
|
1000
970
|
# Forward the request to the downstream app -- middleware only.
|
1001
971
|
def forward
|
1002
|
-
|
972
|
+
raise 'downstream app not set' unless @app.respond_to? :call
|
973
|
+
|
1003
974
|
status, headers, body = @app.call env
|
1004
975
|
@response.status = status
|
1005
976
|
@response.body = body
|
@@ -1021,18 +992,18 @@ module Sinatra
|
|
1021
992
|
|
1022
993
|
# Run routes defined on the class and all superclasses.
|
1023
994
|
def route!(base = settings, pass_block = nil)
|
1024
|
-
|
1025
|
-
routes.each do |pattern, conditions, block|
|
1026
|
-
response.delete_header('Content-Type') unless @pinned_response
|
995
|
+
routes = base.routes[@request.request_method]
|
1027
996
|
|
1028
|
-
|
1029
|
-
|
1030
|
-
route_eval { block[*args] }
|
1031
|
-
end
|
997
|
+
routes&.each do |pattern, conditions, block|
|
998
|
+
response.delete_header('Content-Type') unless @pinned_response
|
1032
999
|
|
1033
|
-
|
1034
|
-
|
1000
|
+
returned_pass_block = process_route(pattern, conditions) do |*args|
|
1001
|
+
env['sinatra.route'] = "#{@request.request_method} #{pattern}"
|
1002
|
+
route_eval { block[*args] }
|
1035
1003
|
end
|
1004
|
+
|
1005
|
+
# don't wipe out pass_block in superclass
|
1006
|
+
pass_block = returned_pass_block if returned_pass_block
|
1036
1007
|
end
|
1037
1008
|
|
1038
1009
|
# Run routes defined in superclass.
|
@@ -1056,15 +1027,17 @@ module Sinatra
|
|
1056
1027
|
# Returns pass block.
|
1057
1028
|
def process_route(pattern, conditions, block = nil, values = [])
|
1058
1029
|
route = @request.path_info
|
1059
|
-
route = '/' if route.empty?
|
1030
|
+
route = '/' if route.empty? && !settings.empty_path_info?
|
1060
1031
|
route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/')
|
1061
|
-
return unless params = pattern.params(route)
|
1062
1032
|
|
1063
|
-
params.
|
1033
|
+
params = pattern.params(route)
|
1034
|
+
return unless params
|
1035
|
+
|
1036
|
+
params.delete('ignore') # TODO: better params handling, maybe turn it into "smart" object or detect changes
|
1064
1037
|
force_encoding(params)
|
1065
|
-
@params = @params.merge(params) if params.any?
|
1038
|
+
@params = @params.merge(params) { |_k, v1, v2| v2 || v1 } if params.any?
|
1066
1039
|
|
1067
|
-
regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? {|subpattern| subpattern.is_a?(Mustermann::Regular)}
|
1040
|
+
regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? { |subpattern| subpattern.is_a?(Mustermann::Regular) })
|
1068
1041
|
if regexp_exists
|
1069
1042
|
captures = pattern.match(route).captures.map { |c| URI_INSTANCE.unescape(c) if c }
|
1070
1043
|
values += captures
|
@@ -1077,7 +1050,7 @@ module Sinatra
|
|
1077
1050
|
conditions.each { |c| throw :pass if c.bind(self).call == false }
|
1078
1051
|
block ? block[self, values] : yield(self, values)
|
1079
1052
|
end
|
1080
|
-
rescue
|
1053
|
+
rescue StandardError
|
1081
1054
|
@env['sinatra.error.params'] = @params
|
1082
1055
|
raise
|
1083
1056
|
ensure
|
@@ -1091,35 +1064,35 @@ module Sinatra
|
|
1091
1064
|
# a NotFound exception. Subclasses can override this method to perform
|
1092
1065
|
# custom route miss logic.
|
1093
1066
|
def route_missing
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
raise NotFound, "#{request.request_method} #{request.path_info}"
|
1098
|
-
end
|
1067
|
+
raise NotFound unless @app
|
1068
|
+
|
1069
|
+
forward
|
1099
1070
|
end
|
1100
1071
|
|
1101
1072
|
# Attempt to serve static files from public directory. Throws :halt when
|
1102
1073
|
# a matching file is found, returns nil otherwise.
|
1103
1074
|
def static!(options = {})
|
1104
1075
|
return if (public_dir = settings.public_folder).nil?
|
1076
|
+
|
1105
1077
|
path = "#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}"
|
1106
1078
|
return unless valid_path?(path)
|
1107
1079
|
|
1108
1080
|
path = File.expand_path(path)
|
1109
|
-
return unless path.start_with?(File.expand_path(public_dir)
|
1081
|
+
return unless path.start_with?("#{File.expand_path(public_dir)}/")
|
1082
|
+
|
1110
1083
|
return unless File.file?(path)
|
1111
1084
|
|
1112
1085
|
env['sinatra.static_file'] = path
|
1113
1086
|
cache_control(*settings.static_cache_control) if settings.static_cache_control?
|
1114
|
-
send_file path, options.merge(:
|
1087
|
+
send_file path, options.merge(disposition: nil)
|
1115
1088
|
end
|
1116
1089
|
|
1117
1090
|
# Run the block with 'throw :halt' support and apply result to the response.
|
1118
|
-
def invoke
|
1119
|
-
res = catch(:halt)
|
1091
|
+
def invoke(&block)
|
1092
|
+
res = catch(:halt, &block)
|
1120
1093
|
|
1121
|
-
res = [res] if Integer === res
|
1122
|
-
if Array === res
|
1094
|
+
res = [res] if (Integer === res) || (String === res)
|
1095
|
+
if (Array === res) && (Integer === res.first)
|
1123
1096
|
res = res.dup
|
1124
1097
|
status(res.shift)
|
1125
1098
|
body(res.pop)
|
@@ -1135,6 +1108,7 @@ module Sinatra
|
|
1135
1108
|
# Avoid passing frozen string in force_encoding
|
1136
1109
|
@params.merge!(@request.params).each do |key, val|
|
1137
1110
|
next unless val.respond_to?(:force_encoding)
|
1111
|
+
|
1138
1112
|
val = val.dup if val.frozen?
|
1139
1113
|
@params[key] = force_encoding(val)
|
1140
1114
|
end
|
@@ -1146,39 +1120,43 @@ module Sinatra
|
|
1146
1120
|
end
|
1147
1121
|
route!
|
1148
1122
|
end
|
1149
|
-
rescue ::Exception =>
|
1150
|
-
invoke { handle_exception!(
|
1123
|
+
rescue ::Exception => e
|
1124
|
+
invoke { handle_exception!(e) }
|
1151
1125
|
ensure
|
1152
1126
|
begin
|
1153
1127
|
filter! :after unless env['sinatra.static_file']
|
1154
|
-
rescue ::Exception =>
|
1155
|
-
invoke { handle_exception!(
|
1128
|
+
rescue ::Exception => e
|
1129
|
+
invoke { handle_exception!(e) } unless @env['sinatra.error']
|
1156
1130
|
end
|
1157
1131
|
end
|
1158
1132
|
|
1159
1133
|
# Error handling during requests.
|
1160
1134
|
def handle_exception!(boom)
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1135
|
+
error_params = @env['sinatra.error.params']
|
1136
|
+
|
1137
|
+
@params = @params.merge(error_params) if error_params
|
1138
|
+
|
1164
1139
|
@env['sinatra.error'] = boom
|
1165
1140
|
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1141
|
+
http_status = if boom.is_a? Sinatra::Error
|
1142
|
+
if boom.respond_to? :http_status
|
1143
|
+
boom.http_status
|
1144
|
+
elsif settings.use_code? && boom.respond_to?(:code)
|
1145
|
+
boom.code
|
1146
|
+
end
|
1147
|
+
end
|
1148
|
+
|
1149
|
+
http_status = 500 unless http_status&.between?(400, 599)
|
1150
|
+
status(http_status)
|
1173
1151
|
|
1174
1152
|
if server_error?
|
1175
1153
|
dump_errors! boom if settings.dump_errors?
|
1176
|
-
raise boom if settings.show_exceptions?
|
1154
|
+
raise boom if settings.show_exceptions? && (settings.show_exceptions != :after_handler)
|
1177
1155
|
elsif not_found?
|
1178
1156
|
headers['X-Cascade'] = 'pass' if settings.x_cascade?
|
1179
1157
|
end
|
1180
1158
|
|
1181
|
-
if res = error_block!(boom.class, boom) || error_block!(status, boom)
|
1159
|
+
if (res = error_block!(boom.class, boom) || error_block!(status, boom))
|
1182
1160
|
return res
|
1183
1161
|
end
|
1184
1162
|
|
@@ -1187,12 +1165,14 @@ module Sinatra
|
|
1187
1165
|
body Rack::Utils.escape_html(boom.message)
|
1188
1166
|
else
|
1189
1167
|
content_type 'text/html'
|
1190
|
-
body
|
1168
|
+
body "<h1>#{not_found? ? 'Not Found' : 'Bad Request'}</h1>"
|
1191
1169
|
end
|
1192
1170
|
end
|
1193
1171
|
|
1194
1172
|
return unless server_error?
|
1195
|
-
|
1173
|
+
|
1174
|
+
raise boom if settings.raise_errors? || settings.show_exceptions?
|
1175
|
+
|
1196
1176
|
error_block! Exception, boom
|
1197
1177
|
end
|
1198
1178
|
|
@@ -1200,7 +1180,10 @@ module Sinatra
|
|
1200
1180
|
def error_block!(key, *block_params)
|
1201
1181
|
base = settings
|
1202
1182
|
while base.respond_to?(:errors)
|
1203
|
-
|
1183
|
+
args_array = base.errors[key]
|
1184
|
+
|
1185
|
+
next base = base.superclass unless args_array
|
1186
|
+
|
1204
1187
|
args_array.reverse_each do |args|
|
1205
1188
|
first = args == args_array.first
|
1206
1189
|
args += [block_params]
|
@@ -1208,55 +1191,45 @@ module Sinatra
|
|
1208
1191
|
return resp unless resp.nil? && !first
|
1209
1192
|
end
|
1210
1193
|
end
|
1211
|
-
return false unless key.respond_to?
|
1194
|
+
return false unless key.respond_to?(:superclass) && (key.superclass < Exception)
|
1195
|
+
|
1212
1196
|
error_block!(key.superclass, *block_params)
|
1213
1197
|
end
|
1214
1198
|
|
1215
1199
|
def dump_errors!(boom)
|
1216
|
-
msg = ["#{Time.now.strftime(
|
1200
|
+
msg = ["#{Time.now.strftime('%Y-%m-%d %H:%M:%S')} - #{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t")
|
1217
1201
|
@env['rack.errors'].puts(msg)
|
1218
1202
|
end
|
1219
1203
|
|
1220
1204
|
class << self
|
1221
1205
|
CALLERS_TO_IGNORE = [ # :nodoc:
|
1222
|
-
|
1223
|
-
/
|
1206
|
+
%r{/sinatra(/(base|main|show_exceptions))?\.rb$}, # all sinatra code
|
1207
|
+
%r{lib/tilt.*\.rb$}, # all tilt code
|
1224
1208
|
/^\(.*\)$/, # generated code
|
1225
|
-
/
|
1209
|
+
%r{rubygems/(custom|core_ext/kernel)_require\.rb$}, # rubygems require hacks
|
1226
1210
|
/active_support/, # active_support require hacks
|
1227
|
-
|
1228
|
-
/<internal
|
1229
|
-
|
1230
|
-
]
|
1231
|
-
|
1232
|
-
# contrary to what the comment said previously, rubinius never supported this
|
1233
|
-
if defined?(RUBY_IGNORE_CALLERS)
|
1234
|
-
warn "RUBY_IGNORE_CALLERS is deprecated and will no longer be supported by Sinatra 2.0"
|
1235
|
-
CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS)
|
1236
|
-
end
|
1211
|
+
%r{bundler(/(?:runtime|inline))?\.rb}, # bundler require hacks
|
1212
|
+
/<internal:/ # internal in ruby >= 1.9.2
|
1213
|
+
].freeze
|
1237
1214
|
|
1238
1215
|
attr_reader :routes, :filters, :templates, :errors
|
1239
1216
|
|
1240
|
-
def callers_to_ignore
|
1241
|
-
CALLERS_TO_IGNORE
|
1242
|
-
end
|
1243
|
-
|
1244
1217
|
# Removes all routes, filters, middleware and extension hooks from the
|
1245
1218
|
# current class (not routes/filters/... defined by its superclass).
|
1246
1219
|
def reset!
|
1247
1220
|
@conditions = []
|
1248
1221
|
@routes = {}
|
1249
|
-
@filters = {:
|
1222
|
+
@filters = { before: [], after: [] }
|
1250
1223
|
@errors = {}
|
1251
1224
|
@middleware = []
|
1252
1225
|
@prototype = nil
|
1253
1226
|
@extensions = []
|
1254
1227
|
|
1255
|
-
if superclass.respond_to?(:templates)
|
1256
|
-
|
1257
|
-
|
1258
|
-
|
1259
|
-
|
1228
|
+
@templates = if superclass.respond_to?(:templates)
|
1229
|
+
Hash.new { |_hash, key| superclass.templates[key] }
|
1230
|
+
else
|
1231
|
+
{}
|
1232
|
+
end
|
1260
1233
|
end
|
1261
1234
|
|
1262
1235
|
# Extension modules registered on this class and all superclasses.
|
@@ -1280,16 +1253,21 @@ module Sinatra
|
|
1280
1253
|
# Sets an option to the given value. If the value is a proc,
|
1281
1254
|
# the proc will be called every time the option is accessed.
|
1282
1255
|
def set(option, value = (not_set = true), ignore_setter = false, &block)
|
1283
|
-
raise ArgumentError if block
|
1284
|
-
|
1256
|
+
raise ArgumentError if block && !not_set
|
1257
|
+
|
1258
|
+
if block
|
1259
|
+
value = block
|
1260
|
+
not_set = false
|
1261
|
+
end
|
1285
1262
|
|
1286
1263
|
if not_set
|
1287
1264
|
raise ArgumentError unless option.respond_to?(:each)
|
1288
|
-
|
1265
|
+
|
1266
|
+
option.each { |k, v| set(k, v) }
|
1289
1267
|
return self
|
1290
1268
|
end
|
1291
1269
|
|
1292
|
-
if respond_to?("#{option}=")
|
1270
|
+
if respond_to?("#{option}=") && !ignore_setter
|
1293
1271
|
return __send__("#{option}=", value)
|
1294
1272
|
end
|
1295
1273
|
|
@@ -1328,7 +1306,7 @@ module Sinatra
|
|
1328
1306
|
# class, or an HTTP status code to specify which errors should be
|
1329
1307
|
# handled.
|
1330
1308
|
def error(*codes, &block)
|
1331
|
-
args = compile!
|
1309
|
+
args = compile! 'ERROR', /.*/, block
|
1332
1310
|
codes = codes.flat_map(&method(:Array))
|
1333
1311
|
codes << Exception if codes.empty?
|
1334
1312
|
codes << Sinatra::NotFound if codes.include?(404)
|
@@ -1354,7 +1332,7 @@ module Sinatra
|
|
1354
1332
|
# Load embedded templates from the file; uses the caller's __FILE__
|
1355
1333
|
# when no file is specified.
|
1356
1334
|
def inline_templates=(file = nil)
|
1357
|
-
file = (
|
1335
|
+
file = (caller_files.first || File.expand_path($0)) if file.nil? || file == true
|
1358
1336
|
|
1359
1337
|
begin
|
1360
1338
|
io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file)
|
@@ -1363,23 +1341,24 @@ module Sinatra
|
|
1363
1341
|
app, data = nil
|
1364
1342
|
end
|
1365
1343
|
|
1366
|
-
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1372
|
-
|
1373
|
-
|
1374
|
-
|
1375
|
-
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
1344
|
+
return unless data
|
1345
|
+
|
1346
|
+
encoding = if app && app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m
|
1347
|
+
$2
|
1348
|
+
else
|
1349
|
+
settings.default_encoding
|
1350
|
+
end
|
1351
|
+
|
1352
|
+
lines = app.count("\n") + 1
|
1353
|
+
template = nil
|
1354
|
+
force_encoding data, encoding
|
1355
|
+
data.each_line do |line|
|
1356
|
+
lines += 1
|
1357
|
+
if line =~ /^@@\s*(.*\S)\s*$/
|
1358
|
+
template = force_encoding(String.new, encoding)
|
1359
|
+
templates[$1.to_sym] = [template, file, lines]
|
1360
|
+
elsif template
|
1361
|
+
template << line
|
1383
1362
|
end
|
1384
1363
|
end
|
1385
1364
|
end
|
@@ -1388,8 +1367,10 @@ module Sinatra
|
|
1388
1367
|
def mime_type(type, value = nil)
|
1389
1368
|
return type if type.nil?
|
1390
1369
|
return type.to_s if type.to_s.include?('/')
|
1391
|
-
|
1370
|
+
|
1371
|
+
type = ".#{type}" unless type.to_s[0] == '.'
|
1392
1372
|
return Rack::Mime.mime_type(type, nil) unless value
|
1373
|
+
|
1393
1374
|
Rack::Mime::MIME_TYPES[type] = value
|
1394
1375
|
end
|
1395
1376
|
|
@@ -1398,7 +1379,7 @@ module Sinatra
|
|
1398
1379
|
# mime_types :js # => ['application/javascript', 'text/javascript']
|
1399
1380
|
def mime_types(type)
|
1400
1381
|
type = mime_type type
|
1401
|
-
type =~
|
1382
|
+
type =~ %r{^application/(xml|javascript)$} ? [type, "text/#{$1}"] : [type]
|
1402
1383
|
end
|
1403
1384
|
|
1404
1385
|
# Define a before filter; runs before all requests within the same
|
@@ -1427,7 +1408,7 @@ module Sinatra
|
|
1427
1408
|
end
|
1428
1409
|
|
1429
1410
|
def public=(value)
|
1430
|
-
warn
|
1411
|
+
warn ':public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead'
|
1431
1412
|
set(:public_folder, value)
|
1432
1413
|
end
|
1433
1414
|
|
@@ -1449,14 +1430,21 @@ module Sinatra
|
|
1449
1430
|
route('HEAD', path, opts, &block)
|
1450
1431
|
end
|
1451
1432
|
|
1452
|
-
def put(path, opts = {}, &
|
1453
|
-
|
1454
|
-
def
|
1455
|
-
|
1456
|
-
def
|
1457
|
-
|
1458
|
-
def
|
1459
|
-
|
1433
|
+
def put(path, opts = {}, &block) route 'PUT', path, opts, &block end
|
1434
|
+
|
1435
|
+
def post(path, opts = {}, &block) route 'POST', path, opts, &block end
|
1436
|
+
|
1437
|
+
def delete(path, opts = {}, &block) route 'DELETE', path, opts, &block end
|
1438
|
+
|
1439
|
+
def head(path, opts = {}, &block) route 'HEAD', path, opts, &block end
|
1440
|
+
|
1441
|
+
def options(path, opts = {}, &block) route 'OPTIONS', path, opts, &block end
|
1442
|
+
|
1443
|
+
def patch(path, opts = {}, &block) route 'PATCH', path, opts, &block end
|
1444
|
+
|
1445
|
+
def link(path, opts = {}, &block) route 'LINK', path, opts, &block end
|
1446
|
+
|
1447
|
+
def unlink(path, opts = {}, &block) route 'UNLINK', path, opts, &block end
|
1460
1448
|
|
1461
1449
|
# Makes the methods defined in the block and in the Modules given
|
1462
1450
|
# in `extensions` available to the handlers and templates
|
@@ -1496,37 +1484,39 @@ module Sinatra
|
|
1496
1484
|
# Stop the self-hosted server if running.
|
1497
1485
|
def quit!
|
1498
1486
|
return unless running?
|
1487
|
+
|
1499
1488
|
# Use Thin's hard #stop! if available, otherwise just #stop.
|
1500
1489
|
running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop
|
1501
|
-
|
1490
|
+
warn '== Sinatra has ended his set (crowd applauds)' unless suppress_messages?
|
1502
1491
|
set :running_server, nil
|
1503
1492
|
set :handler_name, nil
|
1504
1493
|
end
|
1505
1494
|
|
1506
|
-
|
1495
|
+
alias stop! quit!
|
1507
1496
|
|
1508
1497
|
# Run the Sinatra app as a self-hosted server using
|
1509
|
-
# Puma, Mongrel, or WEBrick (in that order). If given a block, will call
|
1498
|
+
# Puma, Falcon, Mongrel, or WEBrick (in that order). If given a block, will call
|
1510
1499
|
# with the constructed handler once we have taken the stage.
|
1511
1500
|
def run!(options = {}, &block)
|
1512
1501
|
return if running?
|
1502
|
+
|
1513
1503
|
set options
|
1514
1504
|
handler = Rack::Handler.pick(server)
|
1515
1505
|
handler_name = handler.name.gsub(/.*::/, '')
|
1516
1506
|
server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
|
1517
|
-
server_settings.merge!(:
|
1507
|
+
server_settings.merge!(Port: port, Host: bind)
|
1518
1508
|
|
1519
1509
|
begin
|
1520
1510
|
start_server(handler, server_settings, handler_name, &block)
|
1521
1511
|
rescue Errno::EADDRINUSE
|
1522
|
-
|
1512
|
+
warn "== Someone is already performing on port #{port}!"
|
1523
1513
|
raise
|
1524
1514
|
ensure
|
1525
1515
|
quit!
|
1526
1516
|
end
|
1527
1517
|
end
|
1528
1518
|
|
1529
|
-
|
1519
|
+
alias start! run!
|
1530
1520
|
|
1531
1521
|
# Check whether the self-hosted server is running or not.
|
1532
1522
|
def running?
|
@@ -1544,8 +1534,8 @@ module Sinatra
|
|
1544
1534
|
# Create a new instance of the class fronted by its middleware
|
1545
1535
|
# pipeline. The object is guaranteed to respond to #call but may not be
|
1546
1536
|
# an instance of the class new was called on.
|
1547
|
-
def new(*args, &
|
1548
|
-
instance = new!(*args, &
|
1537
|
+
def new(*args, &block)
|
1538
|
+
instance = new!(*args, &block)
|
1549
1539
|
Wrapper.new(build(instance).to_app, instance)
|
1550
1540
|
end
|
1551
1541
|
ruby2_keywords :new if respond_to?(:ruby2_keywords, true)
|
@@ -1570,12 +1560,6 @@ module Sinatra
|
|
1570
1560
|
cleaned_caller(1).flatten
|
1571
1561
|
end
|
1572
1562
|
|
1573
|
-
# Like caller_files, but containing Arrays rather than strings with the
|
1574
|
-
# first element being the file, and the second being the line.
|
1575
|
-
def caller_locations
|
1576
|
-
cleaned_caller 2
|
1577
|
-
end
|
1578
|
-
|
1579
1563
|
private
|
1580
1564
|
|
1581
1565
|
# Starts the server by running the Rack Handler.
|
@@ -1586,7 +1570,7 @@ module Sinatra
|
|
1586
1570
|
# Run the instance we created:
|
1587
1571
|
handler.run(self, **server_settings) do |server|
|
1588
1572
|
unless suppress_messages?
|
1589
|
-
|
1573
|
+
warn "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
|
1590
1574
|
end
|
1591
1575
|
|
1592
1576
|
setup_traps
|
@@ -1603,18 +1587,18 @@ module Sinatra
|
|
1603
1587
|
end
|
1604
1588
|
|
1605
1589
|
def setup_traps
|
1606
|
-
|
1607
|
-
at_exit { quit! }
|
1590
|
+
return unless traps?
|
1608
1591
|
|
1609
|
-
|
1610
|
-
old_handler = trap(signal) do
|
1611
|
-
quit!
|
1612
|
-
old_handler.call if old_handler.respond_to?(:call)
|
1613
|
-
end
|
1614
|
-
end
|
1592
|
+
at_exit { quit! }
|
1615
1593
|
|
1616
|
-
|
1594
|
+
%i[INT TERM].each do |signal|
|
1595
|
+
old_handler = trap(signal) do
|
1596
|
+
quit!
|
1597
|
+
old_handler.call if old_handler.respond_to?(:call)
|
1598
|
+
end
|
1617
1599
|
end
|
1600
|
+
|
1601
|
+
set :traps, false
|
1618
1602
|
end
|
1619
1603
|
|
1620
1604
|
# Dynamically defines a method on settings.
|
@@ -1642,18 +1626,21 @@ module Sinatra
|
|
1642
1626
|
end
|
1643
1627
|
end
|
1644
1628
|
end
|
1645
|
-
|
1629
|
+
alias agent user_agent
|
1646
1630
|
|
1647
1631
|
# Condition for matching mimetypes. Accepts file extensions.
|
1648
1632
|
def provides(*types)
|
1649
1633
|
types.map! { |t| mime_types(t) }
|
1650
1634
|
types.flatten!
|
1651
1635
|
condition do
|
1652
|
-
|
1653
|
-
|
1654
|
-
|
1655
|
-
|
1656
|
-
|
1636
|
+
response_content_type = response['Content-Type']
|
1637
|
+
preferred_type = request.preferred_type(types)
|
1638
|
+
|
1639
|
+
if response_content_type
|
1640
|
+
types.include?(response_content_type) || types.include?(response_content_type[/^[^;]+/])
|
1641
|
+
elsif preferred_type
|
1642
|
+
params = (preferred_type.respond_to?(:params) ? preferred_type.params : {})
|
1643
|
+
content_type(preferred_type, params)
|
1657
1644
|
true
|
1658
1645
|
else
|
1659
1646
|
false
|
@@ -1662,7 +1649,7 @@ module Sinatra
|
|
1662
1649
|
end
|
1663
1650
|
|
1664
1651
|
def route(verb, path, options = {}, &block)
|
1665
|
-
enable :empty_path_info if path ==
|
1652
|
+
enable :empty_path_info if path == '' && empty_path_info.nil?
|
1666
1653
|
signature = compile!(verb, path, block, **options)
|
1667
1654
|
(@routes[verb] ||= []) << signature
|
1668
1655
|
invoke_hook(:route_added, verb, path, block)
|
@@ -1691,12 +1678,13 @@ module Sinatra
|
|
1691
1678
|
pattern = compile(path, route_mustermann_opts)
|
1692
1679
|
method_name = "#{verb} #{path}"
|
1693
1680
|
unbound_method = generate_method(method_name, &block)
|
1694
|
-
conditions
|
1695
|
-
|
1696
|
-
|
1697
|
-
proc { |a,
|
1681
|
+
conditions = @conditions
|
1682
|
+
@conditions = []
|
1683
|
+
wrapper = block.arity.zero? ?
|
1684
|
+
proc { |a, _p| unbound_method.bind(a).call } :
|
1685
|
+
proc { |a, p| unbound_method.bind(a).call(*p) }
|
1698
1686
|
|
1699
|
-
[
|
1687
|
+
[pattern, conditions, wrapper]
|
1700
1688
|
end
|
1701
1689
|
|
1702
1690
|
def compile(path, route_mustermann_opts = {})
|
@@ -1714,7 +1702,7 @@ module Sinatra
|
|
1714
1702
|
end
|
1715
1703
|
|
1716
1704
|
def setup_middleware(builder)
|
1717
|
-
middleware.each { |c,a,b| builder.use(c, *a, &b) }
|
1705
|
+
middleware.each { |c, a, b| builder.use(c, *a, &b) }
|
1718
1706
|
end
|
1719
1707
|
|
1720
1708
|
def setup_logging(builder)
|
@@ -1744,9 +1732,10 @@ module Sinatra
|
|
1744
1732
|
|
1745
1733
|
def setup_protection(builder)
|
1746
1734
|
return unless protection?
|
1735
|
+
|
1747
1736
|
options = Hash === protection ? protection.dup : {}
|
1748
1737
|
options = {
|
1749
|
-
img_src:
|
1738
|
+
img_src: "'self' data:",
|
1750
1739
|
font_src: "'self'"
|
1751
1740
|
}.merge options
|
1752
1741
|
|
@@ -1760,6 +1749,7 @@ module Sinatra
|
|
1760
1749
|
|
1761
1750
|
def setup_sessions(builder)
|
1762
1751
|
return unless sessions?
|
1752
|
+
|
1763
1753
|
options = {}
|
1764
1754
|
options[:secret] = session_secret if session_secret?
|
1765
1755
|
options.merge! sessions.to_hash if sessions.respond_to? :to_hash
|
@@ -1788,9 +1778,9 @@ module Sinatra
|
|
1788
1778
|
|
1789
1779
|
# Like Kernel#caller but excluding certain magic entries
|
1790
1780
|
def cleaned_caller(keep = 3)
|
1791
|
-
caller(1)
|
1792
|
-
map!
|
1793
|
-
reject { |file, *_|
|
1781
|
+
caller(1)
|
1782
|
+
.map! { |line| line.split(/:(?=\d|in )/, 3)[0, keep] }
|
1783
|
+
.reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
|
1794
1784
|
end
|
1795
1785
|
end
|
1796
1786
|
|
@@ -1798,6 +1788,7 @@ module Sinatra
|
|
1798
1788
|
# which is UTF-8 by default
|
1799
1789
|
def self.force_encoding(data, encoding = default_encoding)
|
1800
1790
|
return if data == settings || data.is_a?(Tempfile)
|
1791
|
+
|
1801
1792
|
if data.respond_to? :force_encoding
|
1802
1793
|
data.force_encoding(encoding).encode!
|
1803
1794
|
elsif data.respond_to? :each_value
|
@@ -1808,24 +1799,26 @@ module Sinatra
|
|
1808
1799
|
data
|
1809
1800
|
end
|
1810
1801
|
|
1811
|
-
def force_encoding(*args)
|
1802
|
+
def force_encoding(*args)
|
1803
|
+
settings.force_encoding(*args)
|
1804
|
+
end
|
1812
1805
|
|
1813
1806
|
reset!
|
1814
1807
|
|
1815
1808
|
set :environment, (ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym
|
1816
|
-
set :raise_errors,
|
1817
|
-
set :dump_errors,
|
1818
|
-
set :show_exceptions,
|
1809
|
+
set :raise_errors, proc { test? }
|
1810
|
+
set :dump_errors, proc { !test? }
|
1811
|
+
set :show_exceptions, proc { development? }
|
1819
1812
|
set :sessions, false
|
1820
|
-
set :session_store, Rack::
|
1813
|
+
set :session_store, Rack::Protection::EncryptedCookie
|
1821
1814
|
set :logging, false
|
1822
1815
|
set :protection, true
|
1823
1816
|
set :method_override, false
|
1824
1817
|
set :use_code, false
|
1825
|
-
set :default_encoding,
|
1818
|
+
set :default_encoding, 'utf-8'
|
1826
1819
|
set :x_cascade, true
|
1827
1820
|
set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" }
|
1828
|
-
settings.add_charset <<
|
1821
|
+
settings.add_charset << %r{^text/}
|
1829
1822
|
set :mustermann_opts, {}
|
1830
1823
|
set :default_content_type, 'text/html'
|
1831
1824
|
|
@@ -1835,12 +1828,12 @@ module Sinatra
|
|
1835
1828
|
set :session_secret, SecureRandom.hex(64)
|
1836
1829
|
rescue LoadError, NotImplementedError
|
1837
1830
|
# SecureRandom raises a NotImplementedError if no random device is available
|
1838
|
-
set :session_secret,
|
1831
|
+
set :session_secret, format('%064x', Kernel.rand((2**256) - 1))
|
1839
1832
|
end
|
1840
1833
|
|
1841
1834
|
class << self
|
1842
|
-
|
1843
|
-
|
1835
|
+
alias methodoverride? method_override?
|
1836
|
+
alias methodoverride= method_override=
|
1844
1837
|
end
|
1845
1838
|
|
1846
1839
|
set :run, false # start server via at-exit hook?
|
@@ -1848,21 +1841,17 @@ module Sinatra
|
|
1848
1841
|
set :handler_name, nil
|
1849
1842
|
set :traps, true
|
1850
1843
|
set :server, %w[HTTP webrick]
|
1851
|
-
set :bind,
|
1844
|
+
set :bind, proc { development? ? 'localhost' : '0.0.0.0' }
|
1852
1845
|
set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567)
|
1853
1846
|
set :quiet, false
|
1854
1847
|
|
1855
1848
|
ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
|
1856
1849
|
|
1857
|
-
|
1858
|
-
|
1859
|
-
|
1860
|
-
|
1861
|
-
|
1862
|
-
server.unshift 'mongrel' if ruby_engine.nil?
|
1863
|
-
server.unshift 'thin' if ruby_engine != 'jruby'
|
1864
|
-
server.unshift 'trinidad' if ruby_engine == 'jruby'
|
1865
|
-
end
|
1850
|
+
server.unshift 'puma'
|
1851
|
+
server.unshift 'falcon' if ruby_engine != 'jruby'
|
1852
|
+
server.unshift 'mongrel' if ruby_engine.nil?
|
1853
|
+
server.unshift 'thin' if ruby_engine != 'jruby'
|
1854
|
+
server.unshift 'trinidad' if ruby_engine == 'jruby'
|
1866
1855
|
|
1867
1856
|
set :absolute_redirects, true
|
1868
1857
|
set :prefixed_redirects, false
|
@@ -1870,14 +1859,14 @@ module Sinatra
|
|
1870
1859
|
set :strict_paths, true
|
1871
1860
|
|
1872
1861
|
set :app_file, nil
|
1873
|
-
set :root,
|
1874
|
-
set :views,
|
1875
|
-
set :reload_templates,
|
1862
|
+
set :root, proc { app_file && File.expand_path(File.dirname(app_file)) }
|
1863
|
+
set :views, proc { root && File.join(root, 'views') }
|
1864
|
+
set :reload_templates, proc { development? }
|
1876
1865
|
set :lock, false
|
1877
1866
|
set :threaded, true
|
1878
1867
|
|
1879
|
-
set :public_folder,
|
1880
|
-
set :static,
|
1868
|
+
set :public_folder, proc { root && File.join(root, 'public') }
|
1869
|
+
set :static, proc { public_folder && File.exist?(public_folder) }
|
1881
1870
|
set :static_cache_control, false
|
1882
1871
|
|
1883
1872
|
error ::Exception do
|
@@ -1896,7 +1885,7 @@ module Sinatra
|
|
1896
1885
|
error NotFound do
|
1897
1886
|
content_type 'text/html'
|
1898
1887
|
|
1899
|
-
if
|
1888
|
+
if instance_of?(Sinatra::Application)
|
1900
1889
|
code = <<-RUBY.gsub(/^ {12}/, '')
|
1901
1890
|
#{request.request_method.downcase} '#{request.path_info}' do
|
1902
1891
|
"Hello World"
|
@@ -1911,11 +1900,11 @@ module Sinatra
|
|
1911
1900
|
end
|
1912
1901
|
RUBY
|
1913
1902
|
|
1914
|
-
file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(
|
1903
|
+
file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(%r{^/}, '')
|
1915
1904
|
code = "# in #{file}\n#{code}" unless file.empty?
|
1916
1905
|
end
|
1917
1906
|
|
1918
|
-
|
1907
|
+
<<-HTML.gsub(/^ {10}/, '')
|
1919
1908
|
<!DOCTYPE html>
|
1920
1909
|
<html>
|
1921
1910
|
<head>
|
@@ -1927,7 +1916,7 @@ module Sinatra
|
|
1927
1916
|
</head>
|
1928
1917
|
<body>
|
1929
1918
|
<h2>Sinatra doesn’t know this ditty.</h2>
|
1930
|
-
<img src='#{uri
|
1919
|
+
<img src='#{uri '/__sinatra__/404.png'}'>
|
1931
1920
|
<div id="c">
|
1932
1921
|
Try this:
|
1933
1922
|
<pre>#{Rack::Utils.escape_html(code)}</pre>
|
@@ -1947,12 +1936,12 @@ module Sinatra
|
|
1947
1936
|
# top-level. Subclassing Sinatra::Base is highly recommended for
|
1948
1937
|
# modular applications.
|
1949
1938
|
class Application < Base
|
1950
|
-
set :logging,
|
1939
|
+
set :logging, proc { !test? }
|
1951
1940
|
set :method_override, true
|
1952
|
-
set :run,
|
1941
|
+
set :run, proc { !test? }
|
1953
1942
|
set :app_file, nil
|
1954
1943
|
|
1955
|
-
def self.register(*extensions, &block)
|
1944
|
+
def self.register(*extensions, &block) # :nodoc:
|
1956
1945
|
added_methods = extensions.flat_map(&:public_instance_methods)
|
1957
1946
|
Delegator.delegate(*added_methods)
|
1958
1947
|
super(*extensions, &block)
|
@@ -1962,11 +1951,12 @@ module Sinatra
|
|
1962
1951
|
# Sinatra delegation mixin. Mixing this module into an object causes all
|
1963
1952
|
# methods to be delegated to the Sinatra::Application class. Used primarily
|
1964
1953
|
# at the top-level.
|
1965
|
-
module Delegator
|
1954
|
+
module Delegator # :nodoc:
|
1966
1955
|
def self.delegate(*methods)
|
1967
1956
|
methods.each do |method_name|
|
1968
1957
|
define_method(method_name) do |*args, &block|
|
1969
1958
|
return super(*args, &block) if respond_to? method_name
|
1959
|
+
|
1970
1960
|
Delegator.target.send(method_name, *args, &block)
|
1971
1961
|
end
|
1972
1962
|
# ensure keyword argument passing is compatible with ruby >= 2.7
|
@@ -1989,7 +1979,8 @@ module Sinatra
|
|
1989
1979
|
|
1990
1980
|
class Wrapper
|
1991
1981
|
def initialize(stack, instance)
|
1992
|
-
@stack
|
1982
|
+
@stack = stack
|
1983
|
+
@instance = instance
|
1993
1984
|
end
|
1994
1985
|
|
1995
1986
|
def settings
|