sinatra 2.2.0 → 3.1.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.

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
 
@@ -21,21 +19,22 @@ require 'sinatra/version'
21
19
 
22
20
  module Sinatra
23
21
  # The request object. See Rack::Request for more info:
24
- # http://rubydoc.info/github/rack/rack/master/Rack/Request
22
+ # https://rubydoc.info/github/rack/rack/main/Rack/Request
25
23
  class Request < Rack::Request
26
- HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/
27
- HEADER_VALUE_WITH_PARAMS = /(?:(?:\w+|\*)\/(?:\w+(?:\.|\-|\+)?|\*)*)\s*(?:;#{HEADER_PARAM})*/
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'] ||= begin
32
- if @env.include? 'HTTP_ACCEPT' and @env['HTTP_ACCEPT'].to_s != ''
33
- @env['HTTP_ACCEPT'].to_s.scan(HEADER_VALUE_WITH_PARAMS).
34
- map! { |e| AcceptEntry.new(e) }.sort
35
- else
36
- [AcceptEntry.new('*/*')]
37
- end
38
- end
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? "HTTP_X_FORWARDED_HOST"
59
+ @env.include? 'HTTP_X_FORWARDED_HOST'
59
60
  end
60
61
 
61
62
  def safe?
62
- get? or head? or options? or trace?
63
+ get? || head? || options? || trace?
63
64
  end
64
65
 
65
66
  def idempotent?
66
- safe? or put? or delete? or link? or unlink?
67
+ safe? || put? || delete? || link? || unlink?
67
68
  end
68
69
 
69
70
  def link?
70
- request_method == "LINK"
71
+ request_method == 'LINK'
71
72
  end
72
73
 
73
74
  def unlink?
74
- request_method == "UNLINK"
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 = Hash[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 <=> self.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
- [ @q, -@type.count('*'), @params.size ]
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 or to_str.respond_to?(*args)
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 = Hash[params]
140
+ @params = params.to_h
140
141
  end
141
142
 
142
143
  def accepts?(entry)
@@ -150,17 +151,17 @@ module Sinatra
150
151
  def matches_params?(params)
151
152
  return true if @params.empty?
152
153
 
153
- params.all? { |k,v| !@params.has_key?(k) || @params[k] == v }
154
+ params.all? { |k, v| !@params.key?(k) || @params[k] == v }
154
155
  end
155
156
  end
156
157
  end
157
158
 
158
159
  # The response object. See Rack::Response and Rack::Response::Helpers for
159
160
  # more info:
160
- # http://rubydoc.info/github/rack/rack/master/Rack/Response
161
- # http://rubydoc.info/github/rack/rack/master/Rack/Response/Helpers
161
+ # https://rubydoc.info/github/rack/rack/main/Rack/Response
162
+ # https://rubydoc.info/github/rack/rack/main/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 "Content-Length"
179
- headers.delete "Content-Type"
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["Content-Length"] = body.map(&:bytesize).reduce(0, :+).to_s
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["Content-Type"] and not headers["Content-Length"] and Array === body
200
+ headers['Content-Type'] && !headers['Content-Length'] && (Array === body)
200
201
  end
201
202
 
202
203
  def drop_content_info?
203
- informational? or drop_body?
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, callback = app.call(env), env['async.callback']
219
- return result unless callback and async?(*result)
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, status, headers, body)
228
- return unless body.respond_to? :close and env.include? 'async.close'
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, "only supports EventMachine at the moment" unless defined? EventMachine
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, headers, body)
243
+ def async?(status, _headers, body)
239
244
  return true if status == -1
240
- body.respond_to? :callback and body.respond_to? :errback
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
- alias call_without_check call unless method_defined? :call_without_check
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 BadRequest < TypeError #:nodoc:
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 < NameError #:nodoc:
273
+ class NotFound < Error # :nodoc:
265
274
  def http_status; 404 end
266
275
  end
267
276
 
@@ -282,7 +291,7 @@ module Sinatra
282
291
  elsif value
283
292
  # Rack 2.0 returns a Rack::File::Iterator here instead of
284
293
  # Rack::File as it was in the previous API.
285
- unless request.head? || value.is_a?(Rack::File::Iterator) || value.is_a?(Stream)
294
+ unless request.head? || value.is_a?(Rack::Files::Iterator) || value.is_a?(Stream)
286
295
  headers.delete 'Content-Length'
287
296
  end
288
297
  response.body = value
@@ -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' and env["REQUEST_METHOD"] != 'GET'
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\+\.\-]*:/i
320
+ return addr if addr.to_s =~ /\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? or request.port != (request.secure? ? 443 : 80)
316
- host << request.host_with_port
317
- else
318
- host << request.host
319
- end
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 ? addr : request.path_info).to_s
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
- code, body = 500, code.to_str if code.respond_to? :to_str
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
- fail "Unknown media type: %p" % type if mime_type.nil?
382
+ raise format('Unknown media type: %p', type) if mime_type.nil?
383
+
369
384
  mime_type = mime_type.dup
370
- unless params.include? :charset or settings.add_charset.all? { |p| not p === mime_type }
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,36 +396,43 @@ module Sinatra
381
396
  response['Content-Type'] = mime_type
382
397
  end
383
398
 
399
+ # https://html.spec.whatwg.org/#multipart-form-data
400
+ MULTIPART_FORM_DATA_REPLACEMENT_TABLE = {
401
+ '"' => '%22',
402
+ "\r" => '%0D',
403
+ "\n" => '%0A'
404
+ }.freeze
405
+
384
406
  # Set the Content-Disposition to "attachment" with the specified filename,
385
407
  # instructing the user agents to prompt to save.
386
408
  def attachment(filename = nil, disposition = :attachment)
387
409
  response['Content-Disposition'] = disposition.to_s.dup
388
- if filename
389
- params = '; filename="%s"' % File.basename(filename)
390
- response['Content-Disposition'] << params
391
- ext = File.extname(filename)
392
- content_type(ext) unless response['Content-Type'] or ext.empty?
393
- end
410
+ return unless filename
411
+
412
+ params = format('; filename="%s"', File.basename(filename).gsub(/["\r\n]/, MULTIPART_FORM_DATA_REPLACEMENT_TABLE))
413
+ response['Content-Disposition'] << params
414
+ ext = File.extname(filename)
415
+ content_type(ext) unless response['Content-Type'] || ext.empty?
394
416
  end
395
417
 
396
418
  # Use the contents of the file at +path+ as the response body.
397
419
  def send_file(path, opts = {})
398
- if opts[:type] or not response['Content-Type']
399
- content_type opts[:type] || File.extname(path), :default => 'application/octet-stream'
420
+ if opts[:type] || !response['Content-Type']
421
+ content_type opts[:type] || File.extname(path), default: 'application/octet-stream'
400
422
  end
401
423
 
402
424
  disposition = opts[:disposition]
403
425
  filename = opts[:filename]
404
- disposition = :attachment if disposition.nil? and filename
426
+ disposition = :attachment if disposition.nil? && filename
405
427
  filename = path if filename.nil?
406
428
  attachment(filename, disposition) if disposition
407
429
 
408
430
  last_modified opts[:last_modified] if opts[:last_modified]
409
431
 
410
- file = Rack::File.new(File.dirname(settings.app_file))
432
+ file = Rack::Files.new(File.dirname(settings.app_file))
411
433
  result = file.serving(request, path)
412
434
 
413
- result[1].each { |k,v| headers[k] ||= v }
435
+ result[1].each { |k, v| headers[k] ||= v }
414
436
  headers['Content-Length'] = result[1]['Content-Length']
415
437
  opts[:status] &&= Integer(opts[:status])
416
438
  halt (opts[:status] || result[0]), result[2]
@@ -431,12 +453,16 @@ module Sinatra
431
453
  def self.defer(*) yield end
432
454
 
433
455
  def initialize(scheduler = self.class, keep_open = false, &back)
434
- @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open
435
- @callbacks, @closed = [], false
456
+ @back = back.to_proc
457
+ @scheduler = scheduler
458
+ @keep_open = keep_open
459
+ @callbacks = []
460
+ @closed = false
436
461
  end
437
462
 
438
463
  def close
439
464
  return if closed?
465
+
440
466
  @closed = true
441
467
  @scheduler.schedule { @callbacks.each { |c| c.call } }
442
468
  end
@@ -448,8 +474,9 @@ module Sinatra
448
474
  @back.call(self)
449
475
  rescue Exception => e
450
476
  @scheduler.schedule { raise e }
477
+ ensure
478
+ close unless @keep_open
451
479
  end
452
- close unless @keep_open
453
480
  end
454
481
  end
455
482
 
@@ -460,6 +487,7 @@ module Sinatra
460
487
 
461
488
  def callback(&block)
462
489
  return yield if closed?
490
+
463
491
  @callbacks << block
464
492
  end
465
493
 
@@ -479,7 +507,16 @@ module Sinatra
479
507
  def stream(keep_open = false)
480
508
  scheduler = env['async.callback'] ? EventMachine : Stream
481
509
  current = @params.dup
482
- body Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
510
+ stream = if scheduler == Stream && keep_open
511
+ Stream.new(scheduler, false) do |out|
512
+ until out.closed?
513
+ with_params(current) { yield(out) }
514
+ end
515
+ end
516
+ else
517
+ Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
518
+ end
519
+ body stream
483
520
  end
484
521
 
485
522
  # Specify response freshness policy for HTTP caches (Cache-Control header).
@@ -493,18 +530,18 @@ module Sinatra
493
530
  # See RFC 2616 / 14.9 for more on standard cache control directives:
494
531
  # http://tools.ietf.org/html/rfc2616#section-14.9.1
495
532
  def cache_control(*values)
496
- if values.last.kind_of?(Hash)
533
+ if values.last.is_a?(Hash)
497
534
  hash = values.pop
498
- hash.reject! { |k, v| v == false }
535
+ hash.reject! { |_k, v| v == false }
499
536
  hash.reject! { |k, v| values << k if v == true }
500
537
  else
501
538
  hash = {}
502
539
  end
503
540
 
504
- values.map! { |value| value.to_s.tr('_','-') }
541
+ values.map! { |value| value.to_s.tr('_', '-') }
505
542
  hash.each do |key, value|
506
543
  key = key.to_s.tr('_', '-')
507
- value = value.to_i if ['max-age', 's-maxage'].include? key
544
+ value = value.to_i if %w[max-age s-maxage].include? key
508
545
  values << "#{key}=#{value}"
509
546
  end
510
547
 
@@ -521,7 +558,7 @@ module Sinatra
521
558
  # => Expires: Mon, 08 Jun 2009 08:50:17 GMT
522
559
  #
523
560
  def expires(amount, *values)
524
- values << {} unless values.last.kind_of?(Hash)
561
+ values << {} unless values.last.is_a?(Hash)
525
562
 
526
563
  if amount.is_a? Integer
527
564
  time = Time.now + amount.to_i
@@ -531,7 +568,7 @@ module Sinatra
531
568
  max_age = time - Time.now
532
569
  end
533
570
 
534
- values.last.merge!(:max_age => max_age)
571
+ values.last.merge!(max_age: max_age) { |_key, v1, v2| v1 || v2 }
535
572
  cache_control(*values)
536
573
 
537
574
  response['Expires'] = time.httpdate
@@ -546,17 +583,18 @@ module Sinatra
546
583
  # with a '304 Not Modified' response.
547
584
  def last_modified(time)
548
585
  return unless time
586
+
549
587
  time = time_for time
550
588
  response['Last-Modified'] = time.httpdate
551
589
  return if env['HTTP_IF_NONE_MATCH']
552
590
 
553
- if status == 200 and env['HTTP_IF_MODIFIED_SINCE']
591
+ if (status == 200) && env['HTTP_IF_MODIFIED_SINCE']
554
592
  # compare based on seconds since epoch
555
593
  since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
556
594
  halt 304 if since >= time.to_i
557
595
  end
558
596
 
559
- if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE']
597
+ if (success? || (status == 412)) && env['HTTP_IF_UNMODIFIED_SINCE']
560
598
  # compare based on seconds since epoch
561
599
  since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
562
600
  halt 412 if since < time.to_i
@@ -564,7 +602,7 @@ module Sinatra
564
602
  rescue ArgumentError
565
603
  end
566
604
 
567
- ETAG_KINDS = [:strong, :weak]
605
+ ETAG_KINDS = %i[strong weak].freeze
568
606
  # Set the response entity tag (HTTP 'ETag' header) and halt if conditional
569
607
  # GET matches. The +value+ argument is an identifier that uniquely
570
608
  # identifies the current version of the resource. The +kind+ argument
@@ -576,27 +614,31 @@ module Sinatra
576
614
  # GET or HEAD, a '304 Not Modified' response is sent.
577
615
  def etag(value, options = {})
578
616
  # Before touching this code, please double check RFC 2616 14.24 and 14.26.
579
- options = {:kind => options} unless Hash === options
617
+ options = { kind: options } unless Hash === options
580
618
  kind = options[:kind] || :strong
581
619
  new_resource = options.fetch(:new_resource) { request.post? }
582
620
 
583
621
  unless ETAG_KINDS.include?(kind)
584
- raise ArgumentError, ":strong or :weak expected"
622
+ raise ArgumentError, ':strong or :weak expected'
585
623
  end
586
624
 
587
- value = '"%s"' % value
625
+ value = format('"%s"', value)
588
626
  value = "W/#{value}" if kind == :weak
589
627
  response['ETag'] = value
590
628
 
591
- if success? or status == 304
592
- if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
593
- halt(request.safe? ? 304 : 412)
594
- end
629
+ return unless success? || status == 304
595
630
 
596
- if env['HTTP_IF_MATCH']
597
- halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource
598
- end
631
+ if etag_matches?(env['HTTP_IF_NONE_MATCH'], new_resource)
632
+ halt(request.safe? ? 304 : 412)
599
633
  end
634
+
635
+ if env['HTTP_IF_MATCH']
636
+ return if etag_matches?(env['HTTP_IF_MATCH'], new_resource)
637
+
638
+ halt 412
639
+ end
640
+
641
+ nil
600
642
  end
601
643
 
602
644
  # Sugar for redirect (example: redirect back)
@@ -649,8 +691,8 @@ module Sinatra
649
691
  else
650
692
  value.to_time
651
693
  end
652
- rescue ArgumentError => boom
653
- raise boom
694
+ rescue ArgumentError => e
695
+ raise e
654
696
  rescue Exception
655
697
  raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
656
698
  end
@@ -660,11 +702,13 @@ module Sinatra
660
702
  # Helper method checking if a ETag value list includes the current ETag.
661
703
  def etag_matches?(list, new_resource = request.post?)
662
704
  return !new_resource if list == '*'
705
+
663
706
  list.to_s.split(/\s*,\s*/).include? response['ETag']
664
707
  end
665
708
 
666
709
  def with_params(temp_params)
667
- original, @params = @params, temp_params
710
+ original = @params
711
+ @params = temp_params
668
712
  yield
669
713
  ensure
670
714
  @params = original if original
@@ -682,7 +726,7 @@ module Sinatra
682
726
  # Possible options are:
683
727
  # :content_type The content type to use, same arguments as content_type.
684
728
  # :layout If set to something falsy, no layout is rendered, otherwise
685
- # the specified layout is used (Ignored for `sass` and `less`)
729
+ # the specified layout is used (Ignored for `sass`)
686
730
  # :layout_engine Engine to use for rendering the layout.
687
731
  # :locals A hash with local variables that should be available
688
732
  # in the template
@@ -704,36 +748,24 @@ module Sinatra
704
748
  render(:erb, template, options, locals, &block)
705
749
  end
706
750
 
707
- def erubis(template, options = {}, locals = {})
708
- warn "Sinatra::Templates#erubis is deprecated and will be removed, use #erb instead.\n" \
709
- "If you have Erubis installed, it will be used automatically."
710
- render :erubis, template, options, locals
711
- end
712
-
713
751
  def haml(template, options = {}, locals = {}, &block)
714
752
  render(:haml, template, options, locals, &block)
715
753
  end
716
754
 
717
755
  def sass(template, options = {}, locals = {})
718
- options.merge! :layout => false, :default_content_type => :css
756
+ options[:default_content_type] = :css
757
+ options[:exclude_outvar] = true
758
+ options[:layout] = nil
719
759
  render :sass, template, options, locals
720
760
  end
721
761
 
722
762
  def scss(template, options = {}, locals = {})
723
- options.merge! :layout => false, :default_content_type => :css
763
+ options[:default_content_type] = :css
764
+ options[:exclude_outvar] = true
765
+ options[:layout] = nil
724
766
  render :scss, template, options, locals
725
767
  end
726
768
 
727
- def less(template, options = {}, locals = {})
728
- options.merge! :layout => false, :default_content_type => :css
729
- render :less, template, options, locals
730
- end
731
-
732
- def stylus(template, options = {}, locals = {})
733
- options.merge! :layout => false, :default_content_type => :css
734
- render :styl, template, options, locals
735
- end
736
-
737
769
  def builder(template = nil, options = {}, locals = {}, &block)
738
770
  options[:default_content_type] = :xml
739
771
  render_ruby(:builder, template, options, locals, &block)
@@ -748,10 +780,6 @@ module Sinatra
748
780
  render :markdown, template, options, locals
749
781
  end
750
782
 
751
- def textile(template, options = {}, locals = {})
752
- render :textile, template, options, locals
753
- end
754
-
755
783
  def rdoc(template, options = {}, locals = {})
756
784
  render :rdoc, template, options, locals
757
785
  end
@@ -760,19 +788,10 @@ module Sinatra
760
788
  render :asciidoc, template, options, locals
761
789
  end
762
790
 
763
- def radius(template, options = {}, locals = {})
764
- render :radius, template, options, locals
765
- end
766
-
767
791
  def markaby(template = nil, options = {}, locals = {}, &block)
768
792
  render_ruby(:mab, template, options, locals, &block)
769
793
  end
770
794
 
771
- def coffee(template, options = {}, locals = {})
772
- options.merge! :layout => false, :default_content_type => :js
773
- render :coffee, template, options, locals
774
- end
775
-
776
795
  def nokogiri(template = nil, options = {}, locals = {}, &block)
777
796
  options[:default_content_type] = :xml
778
797
  render_ruby(:nokogiri, template, options, locals, &block)
@@ -782,18 +801,6 @@ module Sinatra
782
801
  render(:slim, template, options, locals, &block)
783
802
  end
784
803
 
785
- def creole(template, options = {}, locals = {})
786
- render :creole, template, options, locals
787
- end
788
-
789
- def mediawiki(template, options = {}, locals = {})
790
- render :mediawiki, template, options, locals
791
- end
792
-
793
- def wlang(template, options = {}, locals = {}, &block)
794
- render(:wlang, template, options, locals, &block)
795
- end
796
-
797
804
  def yajl(template, options = {}, locals = {})
798
805
  options[:default_content_type] = :json
799
806
  render :yajl, template, options, locals
@@ -818,24 +825,27 @@ module Sinatra
818
825
 
819
826
  # logic shared between builder and nokogiri
820
827
  def render_ruby(engine, template, options = {}, locals = {}, &block)
821
- options, template = template, nil if template.is_a?(Hash)
822
- template = Proc.new { block } if template.nil?
828
+ if template.is_a?(Hash)
829
+ options = template
830
+ template = nil
831
+ end
832
+ template = proc { block } if template.nil?
823
833
  render engine, template, options, locals
824
834
  end
825
835
 
826
836
  def render(engine, data, options = {}, locals = {}, &block)
827
837
  # merge app-level options
828
838
  engine_options = settings.respond_to?(engine) ? settings.send(engine) : {}
829
- options.merge!(engine_options) { |key, v1, v2| v1 }
839
+ options.merge!(engine_options) { |_key, v1, _v2| v1 }
830
840
 
831
841
  # extract generic options
832
842
  locals = options.delete(:locals) || locals || {}
833
- views = options.delete(:views) || settings.views || "./views"
843
+ views = options.delete(:views) || settings.views || './views'
834
844
  layout = options[:layout]
835
845
  layout = false if layout.nil? && options.include?(:layout)
836
846
  eat_errors = layout.nil?
837
- layout = engine_options[:layout] if layout.nil? or (layout == true && engine_options[:layout] != false)
838
- layout = @default_layout if layout.nil? or layout == true
847
+ layout = engine_options[:layout] if layout.nil? || (layout == true && engine_options[:layout] != false)
848
+ layout = @default_layout if layout.nil? || (layout == true)
839
849
  layout_options = options.delete(:layout_options) || {}
840
850
  content_type = options.delete(:default_content_type)
841
851
  content_type = options.delete(:content_type) || content_type
@@ -860,12 +870,17 @@ module Sinatra
860
870
 
861
871
  # render layout
862
872
  if layout
863
- options = options.merge(:views => views, :layout => false, :eat_errors => eat_errors, :scope => scope).
864
- merge!(layout_options)
873
+ extra_options = { views: views, layout: false, eat_errors: eat_errors, scope: scope }
874
+ options = options.merge(extra_options).merge!(layout_options)
875
+
865
876
  catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } }
866
877
  end
867
878
 
868
- output.extend(ContentTyped).content_type = content_type if content_type
879
+ if content_type
880
+ # sass-embedded returns a frozen string
881
+ output = +output
882
+ output.extend(ContentTyped).content_type = content_type
883
+ end
869
884
  output
870
885
  end
871
886
 
@@ -886,12 +901,13 @@ module Sinatra
886
901
  @preferred_extension = engine.to_s
887
902
  find_template(views, data, template) do |file|
888
903
  path ||= file # keep the initial path rather than the last one
889
- if found = File.exist?(file)
904
+ found = File.exist?(file)
905
+ if found
890
906
  path = file
891
907
  break
892
908
  end
893
909
  end
894
- throw :layout_missing if eat_errors and not found
910
+ throw :layout_missing if eat_errors && !found
895
911
  template.new(path, 1, options)
896
912
  end
897
913
  end
@@ -907,13 +923,44 @@ module Sinatra
907
923
  end
908
924
 
909
925
  def compile_block_template(template, options, &body)
910
- caller = settings.caller_locations.first
911
- path = options[:path] || caller[0]
912
- line = options[:line] || caller[1]
926
+ first_location = caller_locations.first
927
+ path = first_location.path
928
+ line = first_location.lineno
929
+ path = options[:path] || path
930
+ line = options[:line] || line
913
931
  template.new(path, line.to_i, options, &body)
914
932
  end
915
933
  end
916
934
 
935
+ # Extremely simple template cache implementation.
936
+ # * Not thread-safe.
937
+ # * Size is unbounded.
938
+ # * Keys are not copied defensively, and should not be modified after
939
+ # being passed to #fetch. More specifically, the values returned by
940
+ # key#hash and key#eql? should not change.
941
+ #
942
+ # Implementation copied from Tilt::Cache.
943
+ class TemplateCache
944
+ def initialize
945
+ @cache = {}
946
+ end
947
+
948
+ # Caches a value for key, or returns the previously cached value.
949
+ # If a value has been previously cached for key then it is
950
+ # returned. Otherwise, block is yielded to and its return value
951
+ # which may be nil, is cached under key and returned.
952
+ def fetch(*key)
953
+ @cache.fetch(key) do
954
+ @cache[key] = yield
955
+ end
956
+ end
957
+
958
+ # Clears the cache.
959
+ def clear
960
+ @cache = {}
961
+ end
962
+ end
963
+
917
964
  # Base class for all Sinatra applications and middleware.
918
965
  class Base
919
966
  include Rack::Utils
@@ -925,10 +972,10 @@ module Sinatra
925
972
  attr_accessor :app, :env, :request, :response, :params
926
973
  attr_reader :template_cache
927
974
 
928
- def initialize(app = nil, **kwargs)
975
+ def initialize(app = nil, **_kwargs)
929
976
  super()
930
977
  @app = app
931
- @template_cache = Tilt::Cache.new
978
+ @template_cache = TemplateCache.new
932
979
  @pinned_response = nil # whether a before! filter pinned the content-type
933
980
  yield self if block_given?
934
981
  end
@@ -952,7 +999,7 @@ module Sinatra
952
999
  unless @response['Content-Type']
953
1000
  if Array === body && body[0].respond_to?(:content_type)
954
1001
  content_type body[0].content_type
955
- elsif default = settings.default_content_type
1002
+ elsif (default = settings.default_content_type)
956
1003
  content_type default
957
1004
  end
958
1005
  end
@@ -970,12 +1017,6 @@ module Sinatra
970
1017
  self.class.settings
971
1018
  end
972
1019
 
973
- def options
974
- warn "Sinatra::Base#options is deprecated and will be removed, " \
975
- "use #settings instead."
976
- settings
977
- end
978
-
979
1020
  # Exit the current block, halts any further processing
980
1021
  # of the request, and returns the specified response.
981
1022
  def halt(*response)
@@ -992,7 +1033,8 @@ module Sinatra
992
1033
 
993
1034
  # Forward the request to the downstream app -- middleware only.
994
1035
  def forward
995
- fail "downstream app not set" unless @app.respond_to? :call
1036
+ raise 'downstream app not set' unless @app.respond_to? :call
1037
+
996
1038
  status, headers, body = @app.call env
997
1039
  @response.status = status
998
1040
  @response.body = body
@@ -1014,18 +1056,18 @@ module Sinatra
1014
1056
 
1015
1057
  # Run routes defined on the class and all superclasses.
1016
1058
  def route!(base = settings, pass_block = nil)
1017
- if routes = base.routes[@request.request_method]
1018
- routes.each do |pattern, conditions, block|
1019
- response.delete_header('Content-Type') unless @pinned_response
1059
+ routes = base.routes[@request.request_method]
1020
1060
 
1021
- returned_pass_block = process_route(pattern, conditions) do |*args|
1022
- env['sinatra.route'] = "#{@request.request_method} #{pattern}"
1023
- route_eval { block[*args] }
1024
- end
1061
+ routes&.each do |pattern, conditions, block|
1062
+ response.delete_header('Content-Type') unless @pinned_response
1025
1063
 
1026
- # don't wipe out pass_block in superclass
1027
- pass_block = returned_pass_block if returned_pass_block
1064
+ returned_pass_block = process_route(pattern, conditions) do |*args|
1065
+ env['sinatra.route'] = "#{@request.request_method} #{pattern}"
1066
+ route_eval { block[*args] }
1028
1067
  end
1068
+
1069
+ # don't wipe out pass_block in superclass
1070
+ pass_block = returned_pass_block if returned_pass_block
1029
1071
  end
1030
1072
 
1031
1073
  # Run routes defined in superclass.
@@ -1049,15 +1091,17 @@ module Sinatra
1049
1091
  # Returns pass block.
1050
1092
  def process_route(pattern, conditions, block = nil, values = [])
1051
1093
  route = @request.path_info
1052
- route = '/' if route.empty? and not settings.empty_path_info?
1094
+ route = '/' if route.empty? && !settings.empty_path_info?
1053
1095
  route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/')
1054
- return unless params = pattern.params(route)
1055
1096
 
1056
- params.delete("ignore") # TODO: better params handling, maybe turn it into "smart" object or detect changes
1097
+ params = pattern.params(route)
1098
+ return unless params
1099
+
1100
+ params.delete('ignore') # TODO: better params handling, maybe turn it into "smart" object or detect changes
1057
1101
  force_encoding(params)
1058
- @params = @params.merge(params) if params.any?
1102
+ @params = @params.merge(params) { |_k, v1, v2| v2 || v1 } if params.any?
1059
1103
 
1060
- regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? {|subpattern| subpattern.is_a?(Mustermann::Regular)} )
1104
+ regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? { |subpattern| subpattern.is_a?(Mustermann::Regular) })
1061
1105
  if regexp_exists
1062
1106
  captures = pattern.match(route).captures.map { |c| URI_INSTANCE.unescape(c) if c }
1063
1107
  values += captures
@@ -1070,7 +1114,7 @@ module Sinatra
1070
1114
  conditions.each { |c| throw :pass if c.bind(self).call == false }
1071
1115
  block ? block[self, values] : yield(self, values)
1072
1116
  end
1073
- rescue
1117
+ rescue StandardError
1074
1118
  @env['sinatra.error.params'] = @params
1075
1119
  raise
1076
1120
  ensure
@@ -1084,35 +1128,35 @@ module Sinatra
1084
1128
  # a NotFound exception. Subclasses can override this method to perform
1085
1129
  # custom route miss logic.
1086
1130
  def route_missing
1087
- if @app
1088
- forward
1089
- else
1090
- raise NotFound, "#{request.request_method} #{request.path_info}"
1091
- end
1131
+ raise NotFound unless @app
1132
+
1133
+ forward
1092
1134
  end
1093
1135
 
1094
1136
  # Attempt to serve static files from public directory. Throws :halt when
1095
1137
  # a matching file is found, returns nil otherwise.
1096
1138
  def static!(options = {})
1097
1139
  return if (public_dir = settings.public_folder).nil?
1140
+
1098
1141
  path = "#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}"
1099
1142
  return unless valid_path?(path)
1100
1143
 
1101
1144
  path = File.expand_path(path)
1102
- return unless path.start_with?(File.expand_path(public_dir) + '/')
1145
+ return unless path.start_with?("#{File.expand_path(public_dir)}/")
1146
+
1103
1147
  return unless File.file?(path)
1104
1148
 
1105
1149
  env['sinatra.static_file'] = path
1106
1150
  cache_control(*settings.static_cache_control) if settings.static_cache_control?
1107
- send_file path, options.merge(:disposition => nil)
1151
+ send_file path, options.merge(disposition: nil)
1108
1152
  end
1109
1153
 
1110
1154
  # Run the block with 'throw :halt' support and apply result to the response.
1111
- def invoke
1112
- res = catch(:halt) { yield }
1155
+ def invoke(&block)
1156
+ res = catch(:halt, &block)
1113
1157
 
1114
- res = [res] if Integer === res or String === res
1115
- if Array === res and Integer === res.first
1158
+ res = [res] if (Integer === res) || (String === res)
1159
+ if (Array === res) && (Integer === res.first)
1116
1160
  res = res.dup
1117
1161
  status(res.shift)
1118
1162
  body(res.pop)
@@ -1128,6 +1172,7 @@ module Sinatra
1128
1172
  # Avoid passing frozen string in force_encoding
1129
1173
  @params.merge!(@request.params).each do |key, val|
1130
1174
  next unless val.respond_to?(:force_encoding)
1175
+
1131
1176
  val = val.dup if val.frozen?
1132
1177
  @params[key] = force_encoding(val)
1133
1178
  end
@@ -1139,39 +1184,43 @@ module Sinatra
1139
1184
  end
1140
1185
  route!
1141
1186
  end
1142
- rescue ::Exception => boom
1143
- invoke { handle_exception!(boom) }
1187
+ rescue ::Exception => e
1188
+ invoke { handle_exception!(e) }
1144
1189
  ensure
1145
1190
  begin
1146
1191
  filter! :after unless env['sinatra.static_file']
1147
- rescue ::Exception => boom
1148
- invoke { handle_exception!(boom) } unless @env['sinatra.error']
1192
+ rescue ::Exception => e
1193
+ invoke { handle_exception!(e) } unless @env['sinatra.error']
1149
1194
  end
1150
1195
  end
1151
1196
 
1152
1197
  # Error handling during requests.
1153
1198
  def handle_exception!(boom)
1154
- if error_params = @env['sinatra.error.params']
1155
- @params = @params.merge(error_params)
1156
- end
1199
+ error_params = @env['sinatra.error.params']
1200
+
1201
+ @params = @params.merge(error_params) if error_params
1202
+
1157
1203
  @env['sinatra.error'] = boom
1158
1204
 
1159
- if boom.respond_to? :http_status and boom.http_status.between? 400, 599
1160
- status(boom.http_status)
1161
- elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599
1162
- status(boom.code)
1163
- else
1164
- status(500)
1165
- end
1205
+ http_status = if boom.is_a? Sinatra::Error
1206
+ if boom.respond_to? :http_status
1207
+ boom.http_status
1208
+ elsif settings.use_code? && boom.respond_to?(:code)
1209
+ boom.code
1210
+ end
1211
+ end
1212
+
1213
+ http_status = 500 unless http_status&.between?(400, 599)
1214
+ status(http_status)
1166
1215
 
1167
1216
  if server_error?
1168
1217
  dump_errors! boom if settings.dump_errors?
1169
- raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
1218
+ raise boom if settings.show_exceptions? && (settings.show_exceptions != :after_handler)
1170
1219
  elsif not_found?
1171
1220
  headers['X-Cascade'] = 'pass' if settings.x_cascade?
1172
1221
  end
1173
1222
 
1174
- if res = error_block!(boom.class, boom) || error_block!(status, boom)
1223
+ if (res = error_block!(boom.class, boom) || error_block!(status, boom))
1175
1224
  return res
1176
1225
  end
1177
1226
 
@@ -1180,12 +1229,14 @@ module Sinatra
1180
1229
  body Rack::Utils.escape_html(boom.message)
1181
1230
  else
1182
1231
  content_type 'text/html'
1183
- body '<h1>' + (not_found? ? 'Not Found' : 'Bad Request') + '</h1>'
1232
+ body "<h1>#{not_found? ? 'Not Found' : 'Bad Request'}</h1>"
1184
1233
  end
1185
1234
  end
1186
1235
 
1187
1236
  return unless server_error?
1188
- raise boom if settings.raise_errors? or settings.show_exceptions?
1237
+
1238
+ raise boom if settings.raise_errors? || settings.show_exceptions?
1239
+
1189
1240
  error_block! Exception, boom
1190
1241
  end
1191
1242
 
@@ -1193,7 +1244,10 @@ module Sinatra
1193
1244
  def error_block!(key, *block_params)
1194
1245
  base = settings
1195
1246
  while base.respond_to?(:errors)
1196
- next base = base.superclass unless args_array = base.errors[key]
1247
+ args_array = base.errors[key]
1248
+
1249
+ next base = base.superclass unless args_array
1250
+
1197
1251
  args_array.reverse_each do |args|
1198
1252
  first = args == args_array.first
1199
1253
  args += [block_params]
@@ -1201,51 +1255,50 @@ module Sinatra
1201
1255
  return resp unless resp.nil? && !first
1202
1256
  end
1203
1257
  end
1204
- return false unless key.respond_to? :superclass and key.superclass < Exception
1258
+ return false unless key.respond_to?(:superclass) && (key.superclass < Exception)
1259
+
1205
1260
  error_block!(key.superclass, *block_params)
1206
1261
  end
1207
1262
 
1208
1263
  def dump_errors!(boom)
1209
- msg = ["#{Time.now.strftime("%Y-%m-%d %H:%M:%S")} - #{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t")
1264
+ msg = ["#{Time.now.strftime('%Y-%m-%d %H:%M:%S')} - #{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t")
1210
1265
  @env['rack.errors'].puts(msg)
1211
1266
  end
1212
1267
 
1213
1268
  class << self
1214
1269
  CALLERS_TO_IGNORE = [ # :nodoc:
1215
- /\/sinatra(\/(base|main|show_exceptions))?\.rb$/, # all sinatra code
1216
- /lib\/tilt.*\.rb$/, # all tilt code
1270
+ %r{/sinatra(/(base|main|show_exceptions))?\.rb$}, # all sinatra code
1271
+ %r{lib/tilt.*\.rb$}, # all tilt code
1217
1272
  /^\(.*\)$/, # generated code
1218
- /rubygems\/(custom|core_ext\/kernel)_require\.rb$/, # rubygems require hacks
1273
+ %r{rubygems/(custom|core_ext/kernel)_require\.rb$}, # rubygems require hacks
1219
1274
  /active_support/, # active_support require hacks
1220
- /bundler(\/(?:runtime|inline))?\.rb/, # bundler require hacks
1275
+ %r{bundler(/(?:runtime|inline))?\.rb}, # bundler require hacks
1221
1276
  /<internal:/, # internal in ruby >= 1.9.2
1222
- /src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files
1223
- ]
1277
+ %r{zeitwerk/kernel\.rb} # Zeitwerk kernel#require decorator
1278
+ ].freeze
1224
1279
 
1225
- # contrary to what the comment said previously, rubinius never supported this
1226
- if defined?(RUBY_IGNORE_CALLERS)
1227
- warn "RUBY_IGNORE_CALLERS is deprecated and will no longer be supported by Sinatra 2.0"
1228
- CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS)
1229
- end
1280
+ attr_reader :routes, :filters, :templates, :errors, :on_start_callback, :on_stop_callback
1230
1281
 
1231
- attr_reader :routes, :filters, :templates, :errors
1282
+ def callers_to_ignore
1283
+ CALLERS_TO_IGNORE
1284
+ end
1232
1285
 
1233
1286
  # Removes all routes, filters, middleware and extension hooks from the
1234
1287
  # current class (not routes/filters/... defined by its superclass).
1235
1288
  def reset!
1236
1289
  @conditions = []
1237
1290
  @routes = {}
1238
- @filters = {:before => [], :after => []}
1291
+ @filters = { before: [], after: [] }
1239
1292
  @errors = {}
1240
1293
  @middleware = []
1241
1294
  @prototype = nil
1242
1295
  @extensions = []
1243
1296
 
1244
- if superclass.respond_to?(:templates)
1245
- @templates = Hash.new { |hash, key| superclass.templates[key] }
1246
- else
1247
- @templates = {}
1248
- end
1297
+ @templates = if superclass.respond_to?(:templates)
1298
+ Hash.new { |_hash, key| superclass.templates[key] }
1299
+ else
1300
+ {}
1301
+ end
1249
1302
  end
1250
1303
 
1251
1304
  # Extension modules registered on this class and all superclasses.
@@ -1269,16 +1322,21 @@ module Sinatra
1269
1322
  # Sets an option to the given value. If the value is a proc,
1270
1323
  # the proc will be called every time the option is accessed.
1271
1324
  def set(option, value = (not_set = true), ignore_setter = false, &block)
1272
- raise ArgumentError if block and !not_set
1273
- value, not_set = block, false if block
1325
+ raise ArgumentError if block && !not_set
1326
+
1327
+ if block
1328
+ value = block
1329
+ not_set = false
1330
+ end
1274
1331
 
1275
1332
  if not_set
1276
1333
  raise ArgumentError unless option.respond_to?(:each)
1277
- option.each { |k,v| set(k, v) }
1334
+
1335
+ option.each { |k, v| set(k, v) }
1278
1336
  return self
1279
1337
  end
1280
1338
 
1281
- if respond_to?("#{option}=") and not ignore_setter
1339
+ if respond_to?("#{option}=") && !ignore_setter
1282
1340
  return __send__("#{option}=", value)
1283
1341
  end
1284
1342
 
@@ -1317,7 +1375,7 @@ module Sinatra
1317
1375
  # class, or an HTTP status code to specify which errors should be
1318
1376
  # handled.
1319
1377
  def error(*codes, &block)
1320
- args = compile! "ERROR", /.*/, block
1378
+ args = compile! 'ERROR', /.*/, block
1321
1379
  codes = codes.flat_map(&method(:Array))
1322
1380
  codes << Exception if codes.empty?
1323
1381
  codes << Sinatra::NotFound if codes.include?(404)
@@ -1343,7 +1401,7 @@ module Sinatra
1343
1401
  # Load embedded templates from the file; uses the caller's __FILE__
1344
1402
  # when no file is specified.
1345
1403
  def inline_templates=(file = nil)
1346
- file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file
1404
+ file = (caller_files.first || File.expand_path($0)) if file.nil? || file == true
1347
1405
 
1348
1406
  begin
1349
1407
  io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file)
@@ -1352,23 +1410,24 @@ module Sinatra
1352
1410
  app, data = nil
1353
1411
  end
1354
1412
 
1355
- if data
1356
- if app and app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m
1357
- encoding = $2
1358
- else
1359
- encoding = settings.default_encoding
1360
- end
1361
- lines = app.count("\n") + 1
1362
- template = nil
1363
- force_encoding data, encoding
1364
- data.each_line do |line|
1365
- lines += 1
1366
- if line =~ /^@@\s*(.*\S)\s*$/
1367
- template = force_encoding(String.new, encoding)
1368
- templates[$1.to_sym] = [template, file, lines]
1369
- elsif template
1370
- template << line
1371
- end
1413
+ return unless data
1414
+
1415
+ encoding = if app && app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m
1416
+ $2
1417
+ else
1418
+ settings.default_encoding
1419
+ end
1420
+
1421
+ lines = app.count("\n") + 1
1422
+ template = nil
1423
+ force_encoding data, encoding
1424
+ data.each_line do |line|
1425
+ lines += 1
1426
+ if line =~ /^@@\s*(.*\S)\s*$/
1427
+ template = force_encoding(String.new, encoding)
1428
+ templates[$1.to_sym] = [template, file, lines]
1429
+ elsif template
1430
+ template << line
1372
1431
  end
1373
1432
  end
1374
1433
  end
@@ -1377,8 +1436,10 @@ module Sinatra
1377
1436
  def mime_type(type, value = nil)
1378
1437
  return type if type.nil?
1379
1438
  return type.to_s if type.to_s.include?('/')
1380
- type = ".#{type}" unless type.to_s[0] == ?.
1439
+
1440
+ type = ".#{type}" unless type.to_s[0] == '.'
1381
1441
  return Rack::Mime.mime_type(type, nil) unless value
1442
+
1382
1443
  Rack::Mime::MIME_TYPES[type] = value
1383
1444
  end
1384
1445
 
@@ -1387,7 +1448,7 @@ module Sinatra
1387
1448
  # mime_types :js # => ['application/javascript', 'text/javascript']
1388
1449
  def mime_types(type)
1389
1450
  type = mime_type type
1390
- type =~ /^application\/(xml|javascript)$/ ? [type, "text/#$1"] : [type]
1451
+ type =~ %r{^application/(xml|javascript)$} ? [type, "text/#{$1}"] : [type]
1391
1452
  end
1392
1453
 
1393
1454
  # Define a before filter; runs before all requests within the same
@@ -1409,6 +1470,14 @@ module Sinatra
1409
1470
  filters[type] << compile!(type, path, block, **options)
1410
1471
  end
1411
1472
 
1473
+ def on_start(&on_start_callback)
1474
+ @on_start_callback = on_start_callback
1475
+ end
1476
+
1477
+ def on_stop(&on_stop_callback)
1478
+ @on_stop_callback = on_stop_callback
1479
+ end
1480
+
1412
1481
  # Add a route condition. The route is considered non-matching when the
1413
1482
  # block returns false.
1414
1483
  def condition(name = "#{caller.first[/`.*'/]} condition", &block)
@@ -1416,7 +1485,7 @@ module Sinatra
1416
1485
  end
1417
1486
 
1418
1487
  def public=(value)
1419
- warn ":public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead"
1488
+ warn_for_deprecation ':public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead'
1420
1489
  set(:public_folder, value)
1421
1490
  end
1422
1491
 
@@ -1438,14 +1507,21 @@ module Sinatra
1438
1507
  route('HEAD', path, opts, &block)
1439
1508
  end
1440
1509
 
1441
- def put(path, opts = {}, &bk) route 'PUT', path, opts, &bk end
1442
- def post(path, opts = {}, &bk) route 'POST', path, opts, &bk end
1443
- def delete(path, opts = {}, &bk) route 'DELETE', path, opts, &bk end
1444
- def head(path, opts = {}, &bk) route 'HEAD', path, opts, &bk end
1445
- def options(path, opts = {}, &bk) route 'OPTIONS', path, opts, &bk end
1446
- def patch(path, opts = {}, &bk) route 'PATCH', path, opts, &bk end
1447
- def link(path, opts = {}, &bk) route 'LINK', path, opts, &bk end
1448
- def unlink(path, opts = {}, &bk) route 'UNLINK', path, opts, &bk end
1510
+ def put(path, opts = {}, &block) route 'PUT', path, opts, &block end
1511
+
1512
+ def post(path, opts = {}, &block) route 'POST', path, opts, &block end
1513
+
1514
+ def delete(path, opts = {}, &block) route 'DELETE', path, opts, &block end
1515
+
1516
+ def head(path, opts = {}, &block) route 'HEAD', path, opts, &block end
1517
+
1518
+ def options(path, opts = {}, &block) route 'OPTIONS', path, opts, &block end
1519
+
1520
+ def patch(path, opts = {}, &block) route 'PATCH', path, opts, &block end
1521
+
1522
+ def link(path, opts = {}, &block) route 'LINK', path, opts, &block end
1523
+
1524
+ def unlink(path, opts = {}, &block) route 'UNLINK', path, opts, &block end
1449
1525
 
1450
1526
  # Makes the methods defined in the block and in the Modules given
1451
1527
  # in `extensions` available to the handlers and templates
@@ -1485,37 +1561,41 @@ module Sinatra
1485
1561
  # Stop the self-hosted server if running.
1486
1562
  def quit!
1487
1563
  return unless running?
1564
+
1488
1565
  # Use Thin's hard #stop! if available, otherwise just #stop.
1489
1566
  running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop
1490
- $stderr.puts "== Sinatra has ended his set (crowd applauds)" unless suppress_messages?
1567
+ warn '== Sinatra has ended his set (crowd applauds)' unless suppress_messages?
1491
1568
  set :running_server, nil
1492
1569
  set :handler_name, nil
1570
+
1571
+ on_stop_callback.call unless on_stop_callback.nil?
1493
1572
  end
1494
1573
 
1495
- alias_method :stop!, :quit!
1574
+ alias stop! quit!
1496
1575
 
1497
1576
  # Run the Sinatra app as a self-hosted server using
1498
- # Puma, Mongrel, or WEBrick (in that order). If given a block, will call
1577
+ # Puma, Falcon, or WEBrick (in that order). If given a block, will call
1499
1578
  # with the constructed handler once we have taken the stage.
1500
1579
  def run!(options = {}, &block)
1501
1580
  return if running?
1581
+
1502
1582
  set options
1503
1583
  handler = Rack::Handler.pick(server)
1504
1584
  handler_name = handler.name.gsub(/.*::/, '')
1505
1585
  server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
1506
- server_settings.merge!(:Port => port, :Host => bind)
1586
+ server_settings.merge!(Port: port, Host: bind)
1507
1587
 
1508
1588
  begin
1509
1589
  start_server(handler, server_settings, handler_name, &block)
1510
1590
  rescue Errno::EADDRINUSE
1511
- $stderr.puts "== Someone is already performing on port #{port}!"
1591
+ warn "== Someone is already performing on port #{port}!"
1512
1592
  raise
1513
1593
  ensure
1514
1594
  quit!
1515
1595
  end
1516
1596
  end
1517
1597
 
1518
- alias_method :start!, :run!
1598
+ alias start! run!
1519
1599
 
1520
1600
  # Check whether the self-hosted server is running or not.
1521
1601
  def running?
@@ -1533,10 +1613,11 @@ module Sinatra
1533
1613
  # Create a new instance of the class fronted by its middleware
1534
1614
  # pipeline. The object is guaranteed to respond to #call but may not be
1535
1615
  # an instance of the class new was called on.
1536
- def new(*args, **kwargs, &bk)
1537
- instance = new!(*args, **kwargs, &bk)
1616
+ def new(*args, &block)
1617
+ instance = new!(*args, &block)
1538
1618
  Wrapper.new(build(instance).to_app, instance)
1539
1619
  end
1620
+ ruby2_keywords :new if respond_to?(:ruby2_keywords, true)
1540
1621
 
1541
1622
  # Creates a Rack::Builder instance with all the middleware set up and
1542
1623
  # the given +app+ as end point.
@@ -1558,12 +1639,6 @@ module Sinatra
1558
1639
  cleaned_caller(1).flatten
1559
1640
  end
1560
1641
 
1561
- # Like caller_files, but containing Arrays rather than strings with the
1562
- # first element being the file, and the second being the line.
1563
- def caller_locations
1564
- cleaned_caller 2
1565
- end
1566
-
1567
1642
  private
1568
1643
 
1569
1644
  # Starts the server by running the Rack Handler.
@@ -1574,14 +1649,14 @@ module Sinatra
1574
1649
  # Run the instance we created:
1575
1650
  handler.run(self, **server_settings) do |server|
1576
1651
  unless suppress_messages?
1577
- $stderr.puts "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
1652
+ warn "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
1578
1653
  end
1579
1654
 
1580
1655
  setup_traps
1581
1656
  set :running_server, server
1582
1657
  set :handler_name, handler_name
1583
1658
  server.threaded = settings.threaded if server.respond_to? :threaded=
1584
-
1659
+ on_start_callback.call unless on_start_callback.nil?
1585
1660
  yield server if block_given?
1586
1661
  end
1587
1662
  end
@@ -1591,18 +1666,18 @@ module Sinatra
1591
1666
  end
1592
1667
 
1593
1668
  def setup_traps
1594
- if traps?
1595
- at_exit { quit! }
1669
+ return unless traps?
1596
1670
 
1597
- [:INT, :TERM].each do |signal|
1598
- old_handler = trap(signal) do
1599
- quit!
1600
- old_handler.call if old_handler.respond_to?(:call)
1601
- end
1602
- end
1671
+ at_exit { quit! }
1603
1672
 
1604
- set :traps, false
1673
+ %i[INT TERM].each do |signal|
1674
+ old_handler = trap(signal) do
1675
+ quit!
1676
+ old_handler.call if old_handler.respond_to?(:call)
1677
+ end
1605
1678
  end
1679
+
1680
+ set :traps, false
1606
1681
  end
1607
1682
 
1608
1683
  # Dynamically defines a method on settings.
@@ -1630,18 +1705,21 @@ module Sinatra
1630
1705
  end
1631
1706
  end
1632
1707
  end
1633
- alias_method :agent, :user_agent
1708
+ alias agent user_agent
1634
1709
 
1635
1710
  # Condition for matching mimetypes. Accepts file extensions.
1636
1711
  def provides(*types)
1637
1712
  types.map! { |t| mime_types(t) }
1638
1713
  types.flatten!
1639
1714
  condition do
1640
- if type = response['Content-Type']
1641
- types.include? type or types.include? type[/^[^;]+/]
1642
- elsif type = request.preferred_type(types)
1643
- params = (type.respond_to?(:params) ? type.params : {})
1644
- content_type(type, params)
1715
+ response_content_type = response['Content-Type']
1716
+ preferred_type = request.preferred_type(types)
1717
+
1718
+ if response_content_type
1719
+ types.include?(response_content_type) || types.include?(response_content_type[/^[^;]+/])
1720
+ elsif preferred_type
1721
+ params = (preferred_type.respond_to?(:params) ? preferred_type.params : {})
1722
+ content_type(preferred_type, params)
1645
1723
  true
1646
1724
  else
1647
1725
  false
@@ -1650,7 +1728,7 @@ module Sinatra
1650
1728
  end
1651
1729
 
1652
1730
  def route(verb, path, options = {}, &block)
1653
- enable :empty_path_info if path == "" and empty_path_info.nil?
1731
+ enable :empty_path_info if path == '' && empty_path_info.nil?
1654
1732
  signature = compile!(verb, path, block, **options)
1655
1733
  (@routes[verb] ||= []) << signature
1656
1734
  invoke_hook(:route_added, verb, path, block)
@@ -1679,12 +1757,13 @@ module Sinatra
1679
1757
  pattern = compile(path, route_mustermann_opts)
1680
1758
  method_name = "#{verb} #{path}"
1681
1759
  unbound_method = generate_method(method_name, &block)
1682
- conditions, @conditions = @conditions, []
1683
- wrapper = block.arity != 0 ?
1684
- proc { |a, p| unbound_method.bind(a).call(*p) } :
1685
- proc { |a, p| unbound_method.bind(a).call }
1760
+ conditions = @conditions
1761
+ @conditions = []
1762
+ wrapper = block.arity.zero? ?
1763
+ proc { |a, _p| unbound_method.bind(a).call } :
1764
+ proc { |a, p| unbound_method.bind(a).call(*p) }
1686
1765
 
1687
- [ pattern, conditions, wrapper ]
1766
+ [pattern, conditions, wrapper]
1688
1767
  end
1689
1768
 
1690
1769
  def compile(path, route_mustermann_opts = {})
@@ -1702,7 +1781,7 @@ module Sinatra
1702
1781
  end
1703
1782
 
1704
1783
  def setup_middleware(builder)
1705
- middleware.each { |c,a,b| builder.use(c, *a, &b) }
1784
+ middleware.each { |c, a, b| builder.use(c, *a, &b) }
1706
1785
  end
1707
1786
 
1708
1787
  def setup_logging(builder)
@@ -1732,9 +1811,10 @@ module Sinatra
1732
1811
 
1733
1812
  def setup_protection(builder)
1734
1813
  return unless protection?
1814
+
1735
1815
  options = Hash === protection ? protection.dup : {}
1736
1816
  options = {
1737
- img_src: "'self' data:",
1817
+ img_src: "'self' data:",
1738
1818
  font_src: "'self'"
1739
1819
  }.merge options
1740
1820
 
@@ -1748,6 +1828,7 @@ module Sinatra
1748
1828
 
1749
1829
  def setup_sessions(builder)
1750
1830
  return unless sessions?
1831
+
1751
1832
  options = {}
1752
1833
  options[:secret] = session_secret if session_secret?
1753
1834
  options.merge! sessions.to_hash if sessions.respond_to? :to_hash
@@ -1770,15 +1851,15 @@ module Sinatra
1770
1851
  end
1771
1852
 
1772
1853
  # used for deprecation warnings
1773
- def warn(message)
1774
- super message + "\n\tfrom #{cleaned_caller.first.join(':')}"
1854
+ def warn_for_deprecation(message)
1855
+ warn message + "\n\tfrom #{cleaned_caller.first.join(':')}"
1775
1856
  end
1776
1857
 
1777
1858
  # Like Kernel#caller but excluding certain magic entries
1778
1859
  def cleaned_caller(keep = 3)
1779
- caller(1).
1780
- map! { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }.
1781
- reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
1860
+ caller(1)
1861
+ .map! { |line| line.split(/:(?=\d|in )/, 3)[0, keep] }
1862
+ .reject { |file, *_| callers_to_ignore.any? { |pattern| file =~ pattern } }
1782
1863
  end
1783
1864
  end
1784
1865
 
@@ -1786,6 +1867,7 @@ module Sinatra
1786
1867
  # which is UTF-8 by default
1787
1868
  def self.force_encoding(data, encoding = default_encoding)
1788
1869
  return if data == settings || data.is_a?(Tempfile)
1870
+
1789
1871
  if data.respond_to? :force_encoding
1790
1872
  data.force_encoding(encoding).encode!
1791
1873
  elsif data.respond_to? :each_value
@@ -1796,24 +1878,26 @@ module Sinatra
1796
1878
  data
1797
1879
  end
1798
1880
 
1799
- def force_encoding(*args) settings.force_encoding(*args) end
1881
+ def force_encoding(*args)
1882
+ settings.force_encoding(*args)
1883
+ end
1800
1884
 
1801
1885
  reset!
1802
1886
 
1803
1887
  set :environment, (ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym
1804
- set :raise_errors, Proc.new { test? }
1805
- set :dump_errors, Proc.new { !test? }
1806
- set :show_exceptions, Proc.new { development? }
1888
+ set :raise_errors, proc { test? }
1889
+ set :dump_errors, proc { !test? }
1890
+ set :show_exceptions, proc { development? }
1807
1891
  set :sessions, false
1808
- set :session_store, Rack::Session::Cookie
1892
+ set :session_store, Rack::Protection::EncryptedCookie
1809
1893
  set :logging, false
1810
1894
  set :protection, true
1811
1895
  set :method_override, false
1812
1896
  set :use_code, false
1813
- set :default_encoding, "utf-8"
1897
+ set :default_encoding, 'utf-8'
1814
1898
  set :x_cascade, true
1815
1899
  set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" }
1816
- settings.add_charset << /^text\//
1900
+ settings.add_charset << %r{^text/}
1817
1901
  set :mustermann_opts, {}
1818
1902
  set :default_content_type, 'text/html'
1819
1903
 
@@ -1821,14 +1905,15 @@ module Sinatra
1821
1905
  begin
1822
1906
  require 'securerandom'
1823
1907
  set :session_secret, SecureRandom.hex(64)
1824
- rescue LoadError, NotImplementedError
1908
+ rescue LoadError, NotImplementedError, RuntimeError
1825
1909
  # SecureRandom raises a NotImplementedError if no random device is available
1826
- set :session_secret, "%064x" % Kernel.rand(2**256-1)
1910
+ # RuntimeError raised due to broken openssl backend: https://bugs.ruby-lang.org/issues/19230
1911
+ set :session_secret, format('%064x', Kernel.rand((2**256) - 1))
1827
1912
  end
1828
1913
 
1829
1914
  class << self
1830
- alias_method :methodoverride?, :method_override?
1831
- alias_method :methodoverride=, :method_override=
1915
+ alias methodoverride? method_override?
1916
+ alias methodoverride= method_override=
1832
1917
  end
1833
1918
 
1834
1919
  set :run, false # start server via at-exit hook?
@@ -1836,21 +1921,16 @@ module Sinatra
1836
1921
  set :handler_name, nil
1837
1922
  set :traps, true
1838
1923
  set :server, %w[HTTP webrick]
1839
- set :bind, Proc.new { development? ? 'localhost' : '0.0.0.0' }
1924
+ set :bind, proc { development? ? 'localhost' : '0.0.0.0' }
1840
1925
  set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567)
1841
1926
  set :quiet, false
1842
1927
 
1843
1928
  ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
1844
1929
 
1845
- if ruby_engine == 'macruby'
1846
- server.unshift 'control_tower'
1847
- else
1848
- server.unshift 'reel'
1849
- server.unshift 'puma'
1850
- server.unshift 'mongrel' if ruby_engine.nil?
1851
- server.unshift 'thin' if ruby_engine != 'jruby'
1852
- server.unshift 'trinidad' if ruby_engine == 'jruby'
1853
- end
1930
+ server.unshift 'thin' if ruby_engine != 'jruby'
1931
+ server.unshift 'falcon' if ruby_engine != 'jruby'
1932
+ server.unshift 'trinidad' if ruby_engine == 'jruby'
1933
+ server.unshift 'puma'
1854
1934
 
1855
1935
  set :absolute_redirects, true
1856
1936
  set :prefixed_redirects, false
@@ -1858,14 +1938,14 @@ module Sinatra
1858
1938
  set :strict_paths, true
1859
1939
 
1860
1940
  set :app_file, nil
1861
- set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
1862
- set :views, Proc.new { root && File.join(root, 'views') }
1863
- set :reload_templates, Proc.new { development? }
1941
+ set :root, proc { app_file && File.expand_path(File.dirname(app_file)) }
1942
+ set :views, proc { root && File.join(root, 'views') }
1943
+ set :reload_templates, proc { development? }
1864
1944
  set :lock, false
1865
1945
  set :threaded, true
1866
1946
 
1867
- set :public_folder, Proc.new { root && File.join(root, 'public') }
1868
- set :static, Proc.new { public_folder && File.exist?(public_folder) }
1947
+ set :public_folder, proc { root && File.join(root, 'public') }
1948
+ set :static, proc { public_folder && File.exist?(public_folder) }
1869
1949
  set :static_cache_control, false
1870
1950
 
1871
1951
  error ::Exception do
@@ -1884,7 +1964,7 @@ module Sinatra
1884
1964
  error NotFound do
1885
1965
  content_type 'text/html'
1886
1966
 
1887
- if self.class == Sinatra::Application
1967
+ if instance_of?(Sinatra::Application)
1888
1968
  code = <<-RUBY.gsub(/^ {12}/, '')
1889
1969
  #{request.request_method.downcase} '#{request.path_info}' do
1890
1970
  "Hello World"
@@ -1899,11 +1979,11 @@ module Sinatra
1899
1979
  end
1900
1980
  RUBY
1901
1981
 
1902
- file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(/^\//, '')
1982
+ file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(%r{^/}, '')
1903
1983
  code = "# in #{file}\n#{code}" unless file.empty?
1904
1984
  end
1905
1985
 
1906
- (<<-HTML).gsub(/^ {10}/, '')
1986
+ <<-HTML.gsub(/^ {10}/, '')
1907
1987
  <!DOCTYPE html>
1908
1988
  <html>
1909
1989
  <head>
@@ -1915,7 +1995,7 @@ module Sinatra
1915
1995
  </head>
1916
1996
  <body>
1917
1997
  <h2>Sinatra doesn’t know this ditty.</h2>
1918
- <img src='#{uri "/__sinatra__/404.png"}'>
1998
+ <img src='#{uri '/__sinatra__/404.png'}'>
1919
1999
  <div id="c">
1920
2000
  Try this:
1921
2001
  <pre>#{Rack::Utils.escape_html(code)}</pre>
@@ -1935,12 +2015,12 @@ module Sinatra
1935
2015
  # top-level. Subclassing Sinatra::Base is highly recommended for
1936
2016
  # modular applications.
1937
2017
  class Application < Base
1938
- set :logging, Proc.new { !test? }
2018
+ set :logging, proc { !test? }
1939
2019
  set :method_override, true
1940
- set :run, Proc.new { !test? }
2020
+ set :run, proc { !test? }
1941
2021
  set :app_file, nil
1942
2022
 
1943
- def self.register(*extensions, &block) #:nodoc:
2023
+ def self.register(*extensions, &block) # :nodoc:
1944
2024
  added_methods = extensions.flat_map(&:public_instance_methods)
1945
2025
  Delegator.delegate(*added_methods)
1946
2026
  super(*extensions, &block)
@@ -1950,11 +2030,12 @@ module Sinatra
1950
2030
  # Sinatra delegation mixin. Mixing this module into an object causes all
1951
2031
  # methods to be delegated to the Sinatra::Application class. Used primarily
1952
2032
  # at the top-level.
1953
- module Delegator #:nodoc:
2033
+ module Delegator # :nodoc:
1954
2034
  def self.delegate(*methods)
1955
2035
  methods.each do |method_name|
1956
2036
  define_method(method_name) do |*args, &block|
1957
2037
  return super(*args, &block) if respond_to? method_name
2038
+
1958
2039
  Delegator.target.send(method_name, *args, &block)
1959
2040
  end
1960
2041
  # ensure keyword argument passing is compatible with ruby >= 2.7
@@ -1966,7 +2047,7 @@ module Sinatra
1966
2047
  delegate :get, :patch, :put, :post, :delete, :head, :options, :link, :unlink,
1967
2048
  :template, :layout, :before, :after, :error, :not_found, :configure,
1968
2049
  :set, :mime_type, :enable, :disable, :use, :development?, :test?,
1969
- :production?, :helpers, :settings, :register
2050
+ :production?, :helpers, :settings, :register, :on_start, :on_stop
1970
2051
 
1971
2052
  class << self
1972
2053
  attr_accessor :target
@@ -1977,7 +2058,8 @@ module Sinatra
1977
2058
 
1978
2059
  class Wrapper
1979
2060
  def initialize(stack, instance)
1980
- @stack, @instance = stack, instance
2061
+ @stack = stack
2062
+ @instance = instance
1981
2063
  end
1982
2064
 
1983
2065
  def settings