sinatra 2.2.4 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

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?
@@ -208,15 +209,17 @@ module Sinatra
208
209
  end
209
210
  end
210
211
 
211
- # Some Rack handlers (Rainbows!) implement an extended body object protocol, however,
212
+ # Some Rack handlers implement an extended body object protocol, however,
212
213
  # some middleware (namely Rack::Lint) will break it by not mirroring the methods in question.
213
214
  # This middleware will detect an extended body object and will make sure it reaches the
214
215
  # handler directly. We do this here, so our middleware and middleware set up by the app will
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'
@@ -402,22 +417,22 @@ module Sinatra
402
417
 
403
418
  # Use the contents of the file at +path+ as the response body.
404
419
  def send_file(path, opts = {})
405
- if opts[:type] or not response['Content-Type']
406
- 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'
407
422
  end
408
423
 
409
424
  disposition = opts[:disposition]
410
425
  filename = opts[:filename]
411
- disposition = :attachment if disposition.nil? and filename
426
+ disposition = :attachment if disposition.nil? && filename
412
427
  filename = path if filename.nil?
413
428
  attachment(filename, disposition) if disposition
414
429
 
415
430
  last_modified opts[:last_modified] if opts[:last_modified]
416
431
 
417
- file = Rack::File.new(File.dirname(settings.app_file))
432
+ file = Rack::Files.new(File.dirname(settings.app_file))
418
433
  result = file.serving(request, path)
419
434
 
420
- result[1].each { |k,v| headers[k] ||= v }
435
+ result[1].each { |k, v| headers[k] ||= v }
421
436
  headers['Content-Length'] = result[1]['Content-Length']
422
437
  opts[:status] &&= Integer(opts[:status])
423
438
  halt (opts[:status] || result[0]), result[2]
@@ -438,12 +453,16 @@ module Sinatra
438
453
  def self.defer(*) yield end
439
454
 
440
455
  def initialize(scheduler = self.class, keep_open = false, &back)
441
- @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open
442
- @callbacks, @closed = [], false
456
+ @back = back.to_proc
457
+ @scheduler = scheduler
458
+ @keep_open = keep_open
459
+ @callbacks = []
460
+ @closed = false
443
461
  end
444
462
 
445
463
  def close
446
464
  return if closed?
465
+
447
466
  @closed = true
448
467
  @scheduler.schedule { @callbacks.each { |c| c.call } }
449
468
  end
@@ -455,8 +474,9 @@ module Sinatra
455
474
  @back.call(self)
456
475
  rescue Exception => e
457
476
  @scheduler.schedule { raise e }
477
+ ensure
478
+ close unless @keep_open
458
479
  end
459
- close unless @keep_open
460
480
  end
461
481
  end
462
482
 
@@ -467,6 +487,7 @@ module Sinatra
467
487
 
468
488
  def callback(&block)
469
489
  return yield if closed?
490
+
470
491
  @callbacks << block
471
492
  end
472
493
 
@@ -481,12 +502,20 @@ module Sinatra
481
502
  # the response body have not yet been generated.
482
503
  #
483
504
  # The close parameter specifies whether Stream#close should be called
484
- # after the block has been executed. This is only relevant for evented
485
- # servers like Rainbows.
505
+ # after the block has been executed.
486
506
  def stream(keep_open = false)
487
507
  scheduler = env['async.callback'] ? EventMachine : Stream
488
508
  current = @params.dup
489
- body Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
509
+ stream = if scheduler == Stream && keep_open
510
+ Stream.new(scheduler, false) do |out|
511
+ until out.closed?
512
+ with_params(current) { yield(out) }
513
+ end
514
+ end
515
+ else
516
+ Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
517
+ end
518
+ body stream
490
519
  end
491
520
 
492
521
  # Specify response freshness policy for HTTP caches (Cache-Control header).
@@ -500,18 +529,18 @@ module Sinatra
500
529
  # See RFC 2616 / 14.9 for more on standard cache control directives:
501
530
  # http://tools.ietf.org/html/rfc2616#section-14.9.1
502
531
  def cache_control(*values)
503
- if values.last.kind_of?(Hash)
532
+ if values.last.is_a?(Hash)
504
533
  hash = values.pop
505
- hash.reject! { |k, v| v == false }
534
+ hash.reject! { |_k, v| v == false }
506
535
  hash.reject! { |k, v| values << k if v == true }
507
536
  else
508
537
  hash = {}
509
538
  end
510
539
 
511
- values.map! { |value| value.to_s.tr('_','-') }
540
+ values.map! { |value| value.to_s.tr('_', '-') }
512
541
  hash.each do |key, value|
513
542
  key = key.to_s.tr('_', '-')
514
- value = value.to_i if ['max-age', 's-maxage'].include? key
543
+ value = value.to_i if %w[max-age s-maxage].include? key
515
544
  values << "#{key}=#{value}"
516
545
  end
517
546
 
@@ -528,7 +557,7 @@ module Sinatra
528
557
  # => Expires: Mon, 08 Jun 2009 08:50:17 GMT
529
558
  #
530
559
  def expires(amount, *values)
531
- values << {} unless values.last.kind_of?(Hash)
560
+ values << {} unless values.last.is_a?(Hash)
532
561
 
533
562
  if amount.is_a? Integer
534
563
  time = Time.now + amount.to_i
@@ -538,7 +567,7 @@ module Sinatra
538
567
  max_age = time - Time.now
539
568
  end
540
569
 
541
- values.last.merge!(:max_age => max_age)
570
+ values.last.merge!(max_age: max_age) { |_key, v1, v2| v1 || v2 }
542
571
  cache_control(*values)
543
572
 
544
573
  response['Expires'] = time.httpdate
@@ -553,17 +582,18 @@ module Sinatra
553
582
  # with a '304 Not Modified' response.
554
583
  def last_modified(time)
555
584
  return unless time
585
+
556
586
  time = time_for time
557
587
  response['Last-Modified'] = time.httpdate
558
588
  return if env['HTTP_IF_NONE_MATCH']
559
589
 
560
- if status == 200 and env['HTTP_IF_MODIFIED_SINCE']
590
+ if (status == 200) && env['HTTP_IF_MODIFIED_SINCE']
561
591
  # compare based on seconds since epoch
562
592
  since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
563
593
  halt 304 if since >= time.to_i
564
594
  end
565
595
 
566
- if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE']
596
+ if (success? || (status == 412)) && env['HTTP_IF_UNMODIFIED_SINCE']
567
597
  # compare based on seconds since epoch
568
598
  since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
569
599
  halt 412 if since < time.to_i
@@ -571,7 +601,7 @@ module Sinatra
571
601
  rescue ArgumentError
572
602
  end
573
603
 
574
- ETAG_KINDS = [:strong, :weak]
604
+ ETAG_KINDS = %i[strong weak].freeze
575
605
  # Set the response entity tag (HTTP 'ETag' header) and halt if conditional
576
606
  # GET matches. The +value+ argument is an identifier that uniquely
577
607
  # identifies the current version of the resource. The +kind+ argument
@@ -583,27 +613,31 @@ module Sinatra
583
613
  # GET or HEAD, a '304 Not Modified' response is sent.
584
614
  def etag(value, options = {})
585
615
  # Before touching this code, please double check RFC 2616 14.24 and 14.26.
586
- options = {:kind => options} unless Hash === options
616
+ options = { kind: options } unless Hash === options
587
617
  kind = options[:kind] || :strong
588
618
  new_resource = options.fetch(:new_resource) { request.post? }
589
619
 
590
620
  unless ETAG_KINDS.include?(kind)
591
- raise ArgumentError, ":strong or :weak expected"
621
+ raise ArgumentError, ':strong or :weak expected'
592
622
  end
593
623
 
594
- value = '"%s"' % value
624
+ value = format('"%s"', value)
595
625
  value = "W/#{value}" if kind == :weak
596
626
  response['ETag'] = value
597
627
 
598
- if success? or status == 304
599
- if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
600
- halt(request.safe? ? 304 : 412)
601
- end
628
+ return unless success? || status == 304
602
629
 
603
- if env['HTTP_IF_MATCH']
604
- halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource
605
- end
630
+ if etag_matches?(env['HTTP_IF_NONE_MATCH'], new_resource)
631
+ halt(request.safe? ? 304 : 412)
632
+ end
633
+
634
+ if env['HTTP_IF_MATCH']
635
+ return if etag_matches?(env['HTTP_IF_MATCH'], new_resource)
636
+
637
+ halt 412
606
638
  end
639
+
640
+ nil
607
641
  end
608
642
 
609
643
  # Sugar for redirect (example: redirect back)
@@ -656,8 +690,8 @@ module Sinatra
656
690
  else
657
691
  value.to_time
658
692
  end
659
- rescue ArgumentError => boom
660
- raise boom
693
+ rescue ArgumentError => e
694
+ raise e
661
695
  rescue Exception
662
696
  raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
663
697
  end
@@ -667,11 +701,13 @@ module Sinatra
667
701
  # Helper method checking if a ETag value list includes the current ETag.
668
702
  def etag_matches?(list, new_resource = request.post?)
669
703
  return !new_resource if list == '*'
704
+
670
705
  list.to_s.split(/\s*,\s*/).include? response['ETag']
671
706
  end
672
707
 
673
708
  def with_params(temp_params)
674
- original, @params = @params, temp_params
709
+ original = @params
710
+ @params = temp_params
675
711
  yield
676
712
  ensure
677
713
  @params = original if original
@@ -689,7 +725,7 @@ module Sinatra
689
725
  # Possible options are:
690
726
  # :content_type The content type to use, same arguments as content_type.
691
727
  # :layout If set to something falsy, no layout is rendered, otherwise
692
- # the specified layout is used (Ignored for `sass` and `less`)
728
+ # the specified layout is used (Ignored for `sass`)
693
729
  # :layout_engine Engine to use for rendering the layout.
694
730
  # :locals A hash with local variables that should be available
695
731
  # in the template
@@ -711,36 +747,24 @@ module Sinatra
711
747
  render(:erb, template, options, locals, &block)
712
748
  end
713
749
 
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
750
  def haml(template, options = {}, locals = {}, &block)
721
751
  render(:haml, template, options, locals, &block)
722
752
  end
723
753
 
724
754
  def sass(template, options = {}, locals = {})
725
- options.merge! :layout => false, :default_content_type => :css
755
+ options[:default_content_type] = :css
756
+ options[:exclude_outvar] = true
757
+ options[:layout] = nil
726
758
  render :sass, template, options, locals
727
759
  end
728
760
 
729
761
  def scss(template, options = {}, locals = {})
730
- options.merge! :layout => false, :default_content_type => :css
762
+ options[:default_content_type] = :css
763
+ options[:exclude_outvar] = true
764
+ options[:layout] = nil
731
765
  render :scss, template, options, locals
732
766
  end
733
767
 
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
768
  def builder(template = nil, options = {}, locals = {}, &block)
745
769
  options[:default_content_type] = :xml
746
770
  render_ruby(:builder, template, options, locals, &block)
@@ -755,10 +779,6 @@ module Sinatra
755
779
  render :markdown, template, options, locals
756
780
  end
757
781
 
758
- def textile(template, options = {}, locals = {})
759
- render :textile, template, options, locals
760
- end
761
-
762
782
  def rdoc(template, options = {}, locals = {})
763
783
  render :rdoc, template, options, locals
764
784
  end
@@ -767,19 +787,10 @@ module Sinatra
767
787
  render :asciidoc, template, options, locals
768
788
  end
769
789
 
770
- def radius(template, options = {}, locals = {})
771
- render :radius, template, options, locals
772
- end
773
-
774
790
  def markaby(template = nil, options = {}, locals = {}, &block)
775
791
  render_ruby(:mab, template, options, locals, &block)
776
792
  end
777
793
 
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
794
  def nokogiri(template = nil, options = {}, locals = {}, &block)
784
795
  options[:default_content_type] = :xml
785
796
  render_ruby(:nokogiri, template, options, locals, &block)
@@ -789,18 +800,6 @@ module Sinatra
789
800
  render(:slim, template, options, locals, &block)
790
801
  end
791
802
 
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
803
  def yajl(template, options = {}, locals = {})
805
804
  options[:default_content_type] = :json
806
805
  render :yajl, template, options, locals
@@ -825,24 +824,27 @@ module Sinatra
825
824
 
826
825
  # logic shared between builder and nokogiri
827
826
  def render_ruby(engine, template, options = {}, locals = {}, &block)
828
- options, template = template, nil if template.is_a?(Hash)
829
- template = Proc.new { block } if template.nil?
827
+ if template.is_a?(Hash)
828
+ options = template
829
+ template = nil
830
+ end
831
+ template = proc { block } if template.nil?
830
832
  render engine, template, options, locals
831
833
  end
832
834
 
833
835
  def render(engine, data, options = {}, locals = {}, &block)
834
836
  # merge app-level options
835
837
  engine_options = settings.respond_to?(engine) ? settings.send(engine) : {}
836
- options.merge!(engine_options) { |key, v1, v2| v1 }
838
+ options.merge!(engine_options) { |_key, v1, _v2| v1 }
837
839
 
838
840
  # extract generic options
839
841
  locals = options.delete(:locals) || locals || {}
840
- views = options.delete(:views) || settings.views || "./views"
842
+ views = options.delete(:views) || settings.views || './views'
841
843
  layout = options[:layout]
842
844
  layout = false if layout.nil? && options.include?(:layout)
843
845
  eat_errors = layout.nil?
844
- layout = engine_options[:layout] if layout.nil? or (layout == true && engine_options[:layout] != false)
845
- layout = @default_layout if layout.nil? or layout == true
846
+ layout = engine_options[:layout] if layout.nil? || (layout == true && engine_options[:layout] != false)
847
+ layout = @default_layout if layout.nil? || (layout == true)
846
848
  layout_options = options.delete(:layout_options) || {}
847
849
  content_type = options.delete(:default_content_type)
848
850
  content_type = options.delete(:content_type) || content_type
@@ -867,12 +869,17 @@ module Sinatra
867
869
 
868
870
  # render layout
869
871
  if layout
870
- options = options.merge(:views => views, :layout => false, :eat_errors => eat_errors, :scope => scope).
871
- merge!(layout_options)
872
+ extra_options = { views: views, layout: false, eat_errors: eat_errors, scope: scope }
873
+ options = options.merge(extra_options).merge!(layout_options)
874
+
872
875
  catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } }
873
876
  end
874
877
 
875
- output.extend(ContentTyped).content_type = content_type if content_type
878
+ if content_type
879
+ # sass-embedded returns a frozen string
880
+ output = +output
881
+ output.extend(ContentTyped).content_type = content_type
882
+ end
876
883
  output
877
884
  end
878
885
 
@@ -893,12 +900,13 @@ module Sinatra
893
900
  @preferred_extension = engine.to_s
894
901
  find_template(views, data, template) do |file|
895
902
  path ||= file # keep the initial path rather than the last one
896
- if found = File.exist?(file)
903
+ found = File.exist?(file)
904
+ if found
897
905
  path = file
898
906
  break
899
907
  end
900
908
  end
901
- throw :layout_missing if eat_errors and not found
909
+ throw :layout_missing if eat_errors && !found
902
910
  template.new(path, 1, options)
903
911
  end
904
912
  end
@@ -914,13 +922,44 @@ module Sinatra
914
922
  end
915
923
 
916
924
  def compile_block_template(template, options, &body)
917
- caller = settings.caller_locations.first
918
- path = options[:path] || caller[0]
919
- line = options[:line] || caller[1]
925
+ first_location = caller_locations.first
926
+ path = first_location.path
927
+ line = first_location.lineno
928
+ path = options[:path] || path
929
+ line = options[:line] || line
920
930
  template.new(path, line.to_i, options, &body)
921
931
  end
922
932
  end
923
933
 
934
+ # Extremely simple template cache implementation.
935
+ # * Not thread-safe.
936
+ # * Size is unbounded.
937
+ # * Keys are not copied defensively, and should not be modified after
938
+ # being passed to #fetch. More specifically, the values returned by
939
+ # key#hash and key#eql? should not change.
940
+ #
941
+ # Implementation copied from Tilt::Cache.
942
+ class TemplateCache
943
+ def initialize
944
+ @cache = {}
945
+ end
946
+
947
+ # Caches a value for key, or returns the previously cached value.
948
+ # If a value has been previously cached for key then it is
949
+ # returned. Otherwise, block is yielded to and its return value
950
+ # which may be nil, is cached under key and returned.
951
+ def fetch(*key)
952
+ @cache.fetch(key) do
953
+ @cache[key] = yield
954
+ end
955
+ end
956
+
957
+ # Clears the cache.
958
+ def clear
959
+ @cache = {}
960
+ end
961
+ end
962
+
924
963
  # Base class for all Sinatra applications and middleware.
925
964
  class Base
926
965
  include Rack::Utils
@@ -932,10 +971,10 @@ module Sinatra
932
971
  attr_accessor :app, :env, :request, :response, :params
933
972
  attr_reader :template_cache
934
973
 
935
- def initialize(app = nil, **kwargs)
974
+ def initialize(app = nil, **_kwargs)
936
975
  super()
937
976
  @app = app
938
- @template_cache = Tilt::Cache.new
977
+ @template_cache = TemplateCache.new
939
978
  @pinned_response = nil # whether a before! filter pinned the content-type
940
979
  yield self if block_given?
941
980
  end
@@ -959,7 +998,7 @@ module Sinatra
959
998
  unless @response['Content-Type']
960
999
  if Array === body && body[0].respond_to?(:content_type)
961
1000
  content_type body[0].content_type
962
- elsif default = settings.default_content_type
1001
+ elsif (default = settings.default_content_type)
963
1002
  content_type default
964
1003
  end
965
1004
  end
@@ -977,12 +1016,6 @@ module Sinatra
977
1016
  self.class.settings
978
1017
  end
979
1018
 
980
- def options
981
- warn "Sinatra::Base#options is deprecated and will be removed, " \
982
- "use #settings instead."
983
- settings
984
- end
985
-
986
1019
  # Exit the current block, halts any further processing
987
1020
  # of the request, and returns the specified response.
988
1021
  def halt(*response)
@@ -999,7 +1032,8 @@ module Sinatra
999
1032
 
1000
1033
  # Forward the request to the downstream app -- middleware only.
1001
1034
  def forward
1002
- fail "downstream app not set" unless @app.respond_to? :call
1035
+ raise 'downstream app not set' unless @app.respond_to? :call
1036
+
1003
1037
  status, headers, body = @app.call env
1004
1038
  @response.status = status
1005
1039
  @response.body = body
@@ -1021,18 +1055,18 @@ module Sinatra
1021
1055
 
1022
1056
  # Run routes defined on the class and all superclasses.
1023
1057
  def route!(base = settings, pass_block = nil)
1024
- if routes = base.routes[@request.request_method]
1025
- routes.each do |pattern, conditions, block|
1026
- response.delete_header('Content-Type') unless @pinned_response
1058
+ routes = base.routes[@request.request_method]
1027
1059
 
1028
- returned_pass_block = process_route(pattern, conditions) do |*args|
1029
- env['sinatra.route'] = "#{@request.request_method} #{pattern}"
1030
- route_eval { block[*args] }
1031
- end
1060
+ routes&.each do |pattern, conditions, block|
1061
+ response.delete_header('Content-Type') unless @pinned_response
1032
1062
 
1033
- # don't wipe out pass_block in superclass
1034
- pass_block = returned_pass_block if returned_pass_block
1063
+ returned_pass_block = process_route(pattern, conditions) do |*args|
1064
+ env['sinatra.route'] = "#{@request.request_method} #{pattern}"
1065
+ route_eval { block[*args] }
1035
1066
  end
1067
+
1068
+ # don't wipe out pass_block in superclass
1069
+ pass_block = returned_pass_block if returned_pass_block
1036
1070
  end
1037
1071
 
1038
1072
  # Run routes defined in superclass.
@@ -1056,15 +1090,17 @@ module Sinatra
1056
1090
  # Returns pass block.
1057
1091
  def process_route(pattern, conditions, block = nil, values = [])
1058
1092
  route = @request.path_info
1059
- route = '/' if route.empty? and not settings.empty_path_info?
1093
+ route = '/' if route.empty? && !settings.empty_path_info?
1060
1094
  route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/')
1061
- return unless params = pattern.params(route)
1062
1095
 
1063
- params.delete("ignore") # TODO: better params handling, maybe turn it into "smart" object or detect changes
1096
+ params = pattern.params(route)
1097
+ return unless params
1098
+
1099
+ params.delete('ignore') # TODO: better params handling, maybe turn it into "smart" object or detect changes
1064
1100
  force_encoding(params)
1065
- @params = @params.merge(params) if params.any?
1101
+ @params = @params.merge(params) { |_k, v1, v2| v2 || v1 } if params.any?
1066
1102
 
1067
- regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? {|subpattern| subpattern.is_a?(Mustermann::Regular)} )
1103
+ regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? { |subpattern| subpattern.is_a?(Mustermann::Regular) })
1068
1104
  if regexp_exists
1069
1105
  captures = pattern.match(route).captures.map { |c| URI_INSTANCE.unescape(c) if c }
1070
1106
  values += captures
@@ -1077,7 +1113,7 @@ module Sinatra
1077
1113
  conditions.each { |c| throw :pass if c.bind(self).call == false }
1078
1114
  block ? block[self, values] : yield(self, values)
1079
1115
  end
1080
- rescue
1116
+ rescue StandardError
1081
1117
  @env['sinatra.error.params'] = @params
1082
1118
  raise
1083
1119
  ensure
@@ -1091,35 +1127,35 @@ module Sinatra
1091
1127
  # a NotFound exception. Subclasses can override this method to perform
1092
1128
  # custom route miss logic.
1093
1129
  def route_missing
1094
- if @app
1095
- forward
1096
- else
1097
- raise NotFound, "#{request.request_method} #{request.path_info}"
1098
- end
1130
+ raise NotFound unless @app
1131
+
1132
+ forward
1099
1133
  end
1100
1134
 
1101
1135
  # Attempt to serve static files from public directory. Throws :halt when
1102
1136
  # a matching file is found, returns nil otherwise.
1103
1137
  def static!(options = {})
1104
1138
  return if (public_dir = settings.public_folder).nil?
1139
+
1105
1140
  path = "#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}"
1106
1141
  return unless valid_path?(path)
1107
1142
 
1108
1143
  path = File.expand_path(path)
1109
- return unless path.start_with?(File.expand_path(public_dir) + '/')
1144
+ return unless path.start_with?("#{File.expand_path(public_dir)}/")
1145
+
1110
1146
  return unless File.file?(path)
1111
1147
 
1112
1148
  env['sinatra.static_file'] = path
1113
1149
  cache_control(*settings.static_cache_control) if settings.static_cache_control?
1114
- send_file path, options.merge(:disposition => nil)
1150
+ send_file path, options.merge(disposition: nil)
1115
1151
  end
1116
1152
 
1117
1153
  # Run the block with 'throw :halt' support and apply result to the response.
1118
- def invoke
1119
- res = catch(:halt) { yield }
1154
+ def invoke(&block)
1155
+ res = catch(:halt, &block)
1120
1156
 
1121
- res = [res] if Integer === res or String === res
1122
- if Array === res and Integer === res.first
1157
+ res = [res] if (Integer === res) || (String === res)
1158
+ if (Array === res) && (Integer === res.first)
1123
1159
  res = res.dup
1124
1160
  status(res.shift)
1125
1161
  body(res.pop)
@@ -1135,6 +1171,7 @@ module Sinatra
1135
1171
  # Avoid passing frozen string in force_encoding
1136
1172
  @params.merge!(@request.params).each do |key, val|
1137
1173
  next unless val.respond_to?(:force_encoding)
1174
+
1138
1175
  val = val.dup if val.frozen?
1139
1176
  @params[key] = force_encoding(val)
1140
1177
  end
@@ -1146,39 +1183,43 @@ module Sinatra
1146
1183
  end
1147
1184
  route!
1148
1185
  end
1149
- rescue ::Exception => boom
1150
- invoke { handle_exception!(boom) }
1186
+ rescue ::Exception => e
1187
+ invoke { handle_exception!(e) }
1151
1188
  ensure
1152
1189
  begin
1153
1190
  filter! :after unless env['sinatra.static_file']
1154
- rescue ::Exception => boom
1155
- invoke { handle_exception!(boom) } unless @env['sinatra.error']
1191
+ rescue ::Exception => e
1192
+ invoke { handle_exception!(e) } unless @env['sinatra.error']
1156
1193
  end
1157
1194
  end
1158
1195
 
1159
1196
  # Error handling during requests.
1160
1197
  def handle_exception!(boom)
1161
- if error_params = @env['sinatra.error.params']
1162
- @params = @params.merge(error_params)
1163
- end
1198
+ error_params = @env['sinatra.error.params']
1199
+
1200
+ @params = @params.merge(error_params) if error_params
1201
+
1164
1202
  @env['sinatra.error'] = boom
1165
1203
 
1166
- if boom.respond_to? :http_status and boom.http_status.between? 400, 599
1167
- status(boom.http_status)
1168
- elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599
1169
- status(boom.code)
1170
- else
1171
- status(500)
1172
- end
1204
+ http_status = if boom.is_a? Sinatra::Error
1205
+ if boom.respond_to? :http_status
1206
+ boom.http_status
1207
+ elsif settings.use_code? && boom.respond_to?(:code)
1208
+ boom.code
1209
+ end
1210
+ end
1211
+
1212
+ http_status = 500 unless http_status&.between?(400, 599)
1213
+ status(http_status)
1173
1214
 
1174
1215
  if server_error?
1175
1216
  dump_errors! boom if settings.dump_errors?
1176
- raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
1217
+ raise boom if settings.show_exceptions? && (settings.show_exceptions != :after_handler)
1177
1218
  elsif not_found?
1178
1219
  headers['X-Cascade'] = 'pass' if settings.x_cascade?
1179
1220
  end
1180
1221
 
1181
- if res = error_block!(boom.class, boom) || error_block!(status, boom)
1222
+ if (res = error_block!(boom.class, boom) || error_block!(status, boom))
1182
1223
  return res
1183
1224
  end
1184
1225
 
@@ -1187,12 +1228,14 @@ module Sinatra
1187
1228
  body Rack::Utils.escape_html(boom.message)
1188
1229
  else
1189
1230
  content_type 'text/html'
1190
- body '<h1>' + (not_found? ? 'Not Found' : 'Bad Request') + '</h1>'
1231
+ body "<h1>#{not_found? ? 'Not Found' : 'Bad Request'}</h1>"
1191
1232
  end
1192
1233
  end
1193
1234
 
1194
1235
  return unless server_error?
1195
- raise boom if settings.raise_errors? or settings.show_exceptions?
1236
+
1237
+ raise boom if settings.raise_errors? || settings.show_exceptions?
1238
+
1196
1239
  error_block! Exception, boom
1197
1240
  end
1198
1241
 
@@ -1200,7 +1243,10 @@ module Sinatra
1200
1243
  def error_block!(key, *block_params)
1201
1244
  base = settings
1202
1245
  while base.respond_to?(:errors)
1203
- next base = base.superclass unless args_array = base.errors[key]
1246
+ args_array = base.errors[key]
1247
+
1248
+ next base = base.superclass unless args_array
1249
+
1204
1250
  args_array.reverse_each do |args|
1205
1251
  first = args == args_array.first
1206
1252
  args += [block_params]
@@ -1208,34 +1254,42 @@ module Sinatra
1208
1254
  return resp unless resp.nil? && !first
1209
1255
  end
1210
1256
  end
1211
- return false unless key.respond_to? :superclass and key.superclass < Exception
1257
+ return false unless key.respond_to?(:superclass) && (key.superclass < Exception)
1258
+
1212
1259
  error_block!(key.superclass, *block_params)
1213
1260
  end
1214
1261
 
1215
1262
  def dump_errors!(boom)
1216
- msg = ["#{Time.now.strftime("%Y-%m-%d %H:%M:%S")} - #{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t")
1263
+ if boom.respond_to?(:detailed_message)
1264
+ msg = boom.detailed_message(highlight: false)
1265
+ if msg =~ /\A(.*?)(?: \(#{ Regexp.quote(boom.class.to_s) }\))?\n/
1266
+ msg = $1
1267
+ additional_msg = $'.lines(chomp: true)
1268
+ else
1269
+ additional_msg = []
1270
+ end
1271
+ else
1272
+ msg = boom.message
1273
+ additional_msg = []
1274
+ end
1275
+ msg = ["#{Time.now.strftime('%Y-%m-%d %H:%M:%S')} - #{boom.class} - #{msg}:", *additional_msg, *boom.backtrace].join("\n\t")
1217
1276
  @env['rack.errors'].puts(msg)
1218
1277
  end
1219
1278
 
1220
1279
  class << self
1221
1280
  CALLERS_TO_IGNORE = [ # :nodoc:
1222
- /\/sinatra(\/(base|main|show_exceptions))?\.rb$/, # all sinatra code
1223
- /lib\/tilt.*\.rb$/, # all tilt code
1281
+ %r{/sinatra(/(base|main|show_exceptions))?\.rb$}, # all sinatra code
1282
+ %r{lib/tilt.*\.rb$}, # all tilt code
1224
1283
  /^\(.*\)$/, # generated code
1225
- /rubygems\/(custom|core_ext\/kernel)_require\.rb$/, # rubygems require hacks
1284
+ /\/bundled_gems.rb$/, # ruby >= 3.3 with bundler >= 2.5
1285
+ %r{rubygems/(custom|core_ext/kernel)_require\.rb$}, # rubygems require hacks
1226
1286
  /active_support/, # active_support require hacks
1227
- /bundler(\/(?:runtime|inline))?\.rb/, # bundler require hacks
1287
+ %r{bundler(/(?:runtime|inline))?\.rb}, # bundler require hacks
1228
1288
  /<internal:/, # internal in ruby >= 1.9.2
1229
- /src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files
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
1289
+ %r{zeitwerk/kernel\.rb} # Zeitwerk kernel#require decorator
1290
+ ].freeze
1237
1291
 
1238
- attr_reader :routes, :filters, :templates, :errors
1292
+ attr_reader :routes, :filters, :templates, :errors, :on_start_callback, :on_stop_callback
1239
1293
 
1240
1294
  def callers_to_ignore
1241
1295
  CALLERS_TO_IGNORE
@@ -1246,17 +1300,17 @@ module Sinatra
1246
1300
  def reset!
1247
1301
  @conditions = []
1248
1302
  @routes = {}
1249
- @filters = {:before => [], :after => []}
1303
+ @filters = { before: [], after: [] }
1250
1304
  @errors = {}
1251
1305
  @middleware = []
1252
1306
  @prototype = nil
1253
1307
  @extensions = []
1254
1308
 
1255
- if superclass.respond_to?(:templates)
1256
- @templates = Hash.new { |hash, key| superclass.templates[key] }
1257
- else
1258
- @templates = {}
1259
- end
1309
+ @templates = if superclass.respond_to?(:templates)
1310
+ Hash.new { |_hash, key| superclass.templates[key] }
1311
+ else
1312
+ {}
1313
+ end
1260
1314
  end
1261
1315
 
1262
1316
  # Extension modules registered on this class and all superclasses.
@@ -1280,16 +1334,21 @@ module Sinatra
1280
1334
  # Sets an option to the given value. If the value is a proc,
1281
1335
  # the proc will be called every time the option is accessed.
1282
1336
  def set(option, value = (not_set = true), ignore_setter = false, &block)
1283
- raise ArgumentError if block and !not_set
1284
- value, not_set = block, false if block
1337
+ raise ArgumentError if block && !not_set
1338
+
1339
+ if block
1340
+ value = block
1341
+ not_set = false
1342
+ end
1285
1343
 
1286
1344
  if not_set
1287
1345
  raise ArgumentError unless option.respond_to?(:each)
1288
- option.each { |k,v| set(k, v) }
1346
+
1347
+ option.each { |k, v| set(k, v) }
1289
1348
  return self
1290
1349
  end
1291
1350
 
1292
- if respond_to?("#{option}=") and not ignore_setter
1351
+ if respond_to?("#{option}=") && !ignore_setter
1293
1352
  return __send__("#{option}=", value)
1294
1353
  end
1295
1354
 
@@ -1328,7 +1387,7 @@ module Sinatra
1328
1387
  # class, or an HTTP status code to specify which errors should be
1329
1388
  # handled.
1330
1389
  def error(*codes, &block)
1331
- args = compile! "ERROR", /.*/, block
1390
+ args = compile! 'ERROR', /.*/, block
1332
1391
  codes = codes.flat_map(&method(:Array))
1333
1392
  codes << Exception if codes.empty?
1334
1393
  codes << Sinatra::NotFound if codes.include?(404)
@@ -1354,7 +1413,7 @@ module Sinatra
1354
1413
  # Load embedded templates from the file; uses the caller's __FILE__
1355
1414
  # when no file is specified.
1356
1415
  def inline_templates=(file = nil)
1357
- file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file
1416
+ file = (caller_files.first || File.expand_path($0)) if file.nil? || file == true
1358
1417
 
1359
1418
  begin
1360
1419
  io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file)
@@ -1363,23 +1422,24 @@ module Sinatra
1363
1422
  app, data = nil
1364
1423
  end
1365
1424
 
1366
- if data
1367
- if app and app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m
1368
- encoding = $2
1369
- else
1370
- encoding = settings.default_encoding
1371
- end
1372
- lines = app.count("\n") + 1
1373
- template = nil
1374
- force_encoding data, encoding
1375
- data.each_line do |line|
1376
- lines += 1
1377
- if line =~ /^@@\s*(.*\S)\s*$/
1378
- template = force_encoding(String.new, encoding)
1379
- templates[$1.to_sym] = [template, file, lines]
1380
- elsif template
1381
- template << line
1382
- end
1425
+ return unless data
1426
+
1427
+ encoding = if app && app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m
1428
+ $2
1429
+ else
1430
+ settings.default_encoding
1431
+ end
1432
+
1433
+ lines = app.count("\n") + 1
1434
+ template = nil
1435
+ force_encoding data, encoding
1436
+ data.each_line do |line|
1437
+ lines += 1
1438
+ if line =~ /^@@\s*(.*\S)\s*$/
1439
+ template = force_encoding(String.new, encoding)
1440
+ templates[$1.to_sym] = [template, file, lines]
1441
+ elsif template
1442
+ template << line
1383
1443
  end
1384
1444
  end
1385
1445
  end
@@ -1388,8 +1448,10 @@ module Sinatra
1388
1448
  def mime_type(type, value = nil)
1389
1449
  return type if type.nil?
1390
1450
  return type.to_s if type.to_s.include?('/')
1391
- type = ".#{type}" unless type.to_s[0] == ?.
1451
+
1452
+ type = ".#{type}" unless type.to_s[0] == '.'
1392
1453
  return Rack::Mime.mime_type(type, nil) unless value
1454
+
1393
1455
  Rack::Mime::MIME_TYPES[type] = value
1394
1456
  end
1395
1457
 
@@ -1398,7 +1460,7 @@ module Sinatra
1398
1460
  # mime_types :js # => ['application/javascript', 'text/javascript']
1399
1461
  def mime_types(type)
1400
1462
  type = mime_type type
1401
- type =~ /^application\/(xml|javascript)$/ ? [type, "text/#$1"] : [type]
1463
+ type =~ %r{^application/(xml|javascript)$} ? [type, "text/#{$1}"] : [type]
1402
1464
  end
1403
1465
 
1404
1466
  # Define a before filter; runs before all requests within the same
@@ -1420,6 +1482,14 @@ module Sinatra
1420
1482
  filters[type] << compile!(type, path, block, **options)
1421
1483
  end
1422
1484
 
1485
+ def on_start(&on_start_callback)
1486
+ @on_start_callback = on_start_callback
1487
+ end
1488
+
1489
+ def on_stop(&on_stop_callback)
1490
+ @on_stop_callback = on_stop_callback
1491
+ end
1492
+
1423
1493
  # Add a route condition. The route is considered non-matching when the
1424
1494
  # block returns false.
1425
1495
  def condition(name = "#{caller.first[/`.*'/]} condition", &block)
@@ -1427,7 +1497,7 @@ module Sinatra
1427
1497
  end
1428
1498
 
1429
1499
  def public=(value)
1430
- warn ":public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead"
1500
+ warn_for_deprecation ':public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead'
1431
1501
  set(:public_folder, value)
1432
1502
  end
1433
1503
 
@@ -1449,14 +1519,21 @@ module Sinatra
1449
1519
  route('HEAD', path, opts, &block)
1450
1520
  end
1451
1521
 
1452
- def put(path, opts = {}, &bk) route 'PUT', path, opts, &bk end
1453
- def post(path, opts = {}, &bk) route 'POST', path, opts, &bk end
1454
- def delete(path, opts = {}, &bk) route 'DELETE', path, opts, &bk end
1455
- def head(path, opts = {}, &bk) route 'HEAD', path, opts, &bk end
1456
- def options(path, opts = {}, &bk) route 'OPTIONS', path, opts, &bk end
1457
- def patch(path, opts = {}, &bk) route 'PATCH', path, opts, &bk end
1458
- def link(path, opts = {}, &bk) route 'LINK', path, opts, &bk end
1459
- def unlink(path, opts = {}, &bk) route 'UNLINK', path, opts, &bk end
1522
+ def put(path, opts = {}, &block) route 'PUT', path, opts, &block end
1523
+
1524
+ def post(path, opts = {}, &block) route 'POST', path, opts, &block end
1525
+
1526
+ def delete(path, opts = {}, &block) route 'DELETE', path, opts, &block end
1527
+
1528
+ def head(path, opts = {}, &block) route 'HEAD', path, opts, &block end
1529
+
1530
+ def options(path, opts = {}, &block) route 'OPTIONS', path, opts, &block end
1531
+
1532
+ def patch(path, opts = {}, &block) route 'PATCH', path, opts, &block end
1533
+
1534
+ def link(path, opts = {}, &block) route 'LINK', path, opts, &block end
1535
+
1536
+ def unlink(path, opts = {}, &block) route 'UNLINK', path, opts, &block end
1460
1537
 
1461
1538
  # Makes the methods defined in the block and in the Modules given
1462
1539
  # in `extensions` available to the handlers and templates
@@ -1496,37 +1573,41 @@ module Sinatra
1496
1573
  # Stop the self-hosted server if running.
1497
1574
  def quit!
1498
1575
  return unless running?
1576
+
1499
1577
  # Use Thin's hard #stop! if available, otherwise just #stop.
1500
1578
  running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop
1501
- $stderr.puts "== Sinatra has ended his set (crowd applauds)" unless suppress_messages?
1579
+ warn '== Sinatra has ended his set (crowd applauds)' unless suppress_messages?
1502
1580
  set :running_server, nil
1503
1581
  set :handler_name, nil
1582
+
1583
+ on_stop_callback.call unless on_stop_callback.nil?
1504
1584
  end
1505
1585
 
1506
- alias_method :stop!, :quit!
1586
+ alias stop! quit!
1507
1587
 
1508
1588
  # Run the Sinatra app as a self-hosted server using
1509
- # Puma, Mongrel, or WEBrick (in that order). If given a block, will call
1589
+ # Puma, Falcon, or WEBrick (in that order). If given a block, will call
1510
1590
  # with the constructed handler once we have taken the stage.
1511
1591
  def run!(options = {}, &block)
1512
1592
  return if running?
1593
+
1513
1594
  set options
1514
1595
  handler = Rack::Handler.pick(server)
1515
1596
  handler_name = handler.name.gsub(/.*::/, '')
1516
1597
  server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
1517
- server_settings.merge!(:Port => port, :Host => bind)
1598
+ server_settings.merge!(Port: port, Host: bind)
1518
1599
 
1519
1600
  begin
1520
1601
  start_server(handler, server_settings, handler_name, &block)
1521
1602
  rescue Errno::EADDRINUSE
1522
- $stderr.puts "== Someone is already performing on port #{port}!"
1603
+ warn "== Someone is already performing on port #{port}!"
1523
1604
  raise
1524
1605
  ensure
1525
1606
  quit!
1526
1607
  end
1527
1608
  end
1528
1609
 
1529
- alias_method :start!, :run!
1610
+ alias start! run!
1530
1611
 
1531
1612
  # Check whether the self-hosted server is running or not.
1532
1613
  def running?
@@ -1544,8 +1625,8 @@ module Sinatra
1544
1625
  # Create a new instance of the class fronted by its middleware
1545
1626
  # pipeline. The object is guaranteed to respond to #call but may not be
1546
1627
  # an instance of the class new was called on.
1547
- def new(*args, &bk)
1548
- instance = new!(*args, &bk)
1628
+ def new(*args, &block)
1629
+ instance = new!(*args, &block)
1549
1630
  Wrapper.new(build(instance).to_app, instance)
1550
1631
  end
1551
1632
  ruby2_keywords :new if respond_to?(:ruby2_keywords, true)
@@ -1570,12 +1651,6 @@ module Sinatra
1570
1651
  cleaned_caller(1).flatten
1571
1652
  end
1572
1653
 
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
1654
  private
1580
1655
 
1581
1656
  # Starts the server by running the Rack Handler.
@@ -1586,14 +1661,14 @@ module Sinatra
1586
1661
  # Run the instance we created:
1587
1662
  handler.run(self, **server_settings) do |server|
1588
1663
  unless suppress_messages?
1589
- $stderr.puts "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
1664
+ warn "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
1590
1665
  end
1591
1666
 
1592
1667
  setup_traps
1593
1668
  set :running_server, server
1594
1669
  set :handler_name, handler_name
1595
1670
  server.threaded = settings.threaded if server.respond_to? :threaded=
1596
-
1671
+ on_start_callback.call unless on_start_callback.nil?
1597
1672
  yield server if block_given?
1598
1673
  end
1599
1674
  end
@@ -1603,18 +1678,18 @@ module Sinatra
1603
1678
  end
1604
1679
 
1605
1680
  def setup_traps
1606
- if traps?
1607
- at_exit { quit! }
1681
+ return unless traps?
1608
1682
 
1609
- [:INT, :TERM].each do |signal|
1610
- old_handler = trap(signal) do
1611
- quit!
1612
- old_handler.call if old_handler.respond_to?(:call)
1613
- end
1614
- end
1683
+ at_exit { quit! }
1615
1684
 
1616
- set :traps, false
1685
+ %i[INT TERM].each do |signal|
1686
+ old_handler = trap(signal) do
1687
+ quit!
1688
+ old_handler.call if old_handler.respond_to?(:call)
1689
+ end
1617
1690
  end
1691
+
1692
+ set :traps, false
1618
1693
  end
1619
1694
 
1620
1695
  # Dynamically defines a method on settings.
@@ -1642,18 +1717,21 @@ module Sinatra
1642
1717
  end
1643
1718
  end
1644
1719
  end
1645
- alias_method :agent, :user_agent
1720
+ alias agent user_agent
1646
1721
 
1647
1722
  # Condition for matching mimetypes. Accepts file extensions.
1648
1723
  def provides(*types)
1649
1724
  types.map! { |t| mime_types(t) }
1650
1725
  types.flatten!
1651
1726
  condition do
1652
- if type = response['Content-Type']
1653
- types.include? type or types.include? type[/^[^;]+/]
1654
- elsif type = request.preferred_type(types)
1655
- params = (type.respond_to?(:params) ? type.params : {})
1656
- content_type(type, params)
1727
+ response_content_type = response['Content-Type']
1728
+ preferred_type = request.preferred_type(types)
1729
+
1730
+ if response_content_type
1731
+ types.include?(response_content_type) || types.include?(response_content_type[/^[^;]+/])
1732
+ elsif preferred_type
1733
+ params = (preferred_type.respond_to?(:params) ? preferred_type.params : {})
1734
+ content_type(preferred_type, params)
1657
1735
  true
1658
1736
  else
1659
1737
  false
@@ -1662,7 +1740,7 @@ module Sinatra
1662
1740
  end
1663
1741
 
1664
1742
  def route(verb, path, options = {}, &block)
1665
- enable :empty_path_info if path == "" and empty_path_info.nil?
1743
+ enable :empty_path_info if path == '' && empty_path_info.nil?
1666
1744
  signature = compile!(verb, path, block, **options)
1667
1745
  (@routes[verb] ||= []) << signature
1668
1746
  invoke_hook(:route_added, verb, path, block)
@@ -1691,12 +1769,13 @@ module Sinatra
1691
1769
  pattern = compile(path, route_mustermann_opts)
1692
1770
  method_name = "#{verb} #{path}"
1693
1771
  unbound_method = generate_method(method_name, &block)
1694
- conditions, @conditions = @conditions, []
1695
- wrapper = block.arity != 0 ?
1696
- proc { |a, p| unbound_method.bind(a).call(*p) } :
1697
- proc { |a, p| unbound_method.bind(a).call }
1772
+ conditions = @conditions
1773
+ @conditions = []
1774
+ wrapper = block.arity.zero? ?
1775
+ proc { |a, _p| unbound_method.bind(a).call } :
1776
+ proc { |a, p| unbound_method.bind(a).call(*p) }
1698
1777
 
1699
- [ pattern, conditions, wrapper ]
1778
+ [pattern, conditions, wrapper]
1700
1779
  end
1701
1780
 
1702
1781
  def compile(path, route_mustermann_opts = {})
@@ -1714,7 +1793,7 @@ module Sinatra
1714
1793
  end
1715
1794
 
1716
1795
  def setup_middleware(builder)
1717
- middleware.each { |c,a,b| builder.use(c, *a, &b) }
1796
+ middleware.each { |c, a, b| builder.use(c, *a, &b) }
1718
1797
  end
1719
1798
 
1720
1799
  def setup_logging(builder)
@@ -1744,9 +1823,10 @@ module Sinatra
1744
1823
 
1745
1824
  def setup_protection(builder)
1746
1825
  return unless protection?
1826
+
1747
1827
  options = Hash === protection ? protection.dup : {}
1748
1828
  options = {
1749
- img_src: "'self' data:",
1829
+ img_src: "'self' data:",
1750
1830
  font_src: "'self'"
1751
1831
  }.merge options
1752
1832
 
@@ -1760,6 +1840,7 @@ module Sinatra
1760
1840
 
1761
1841
  def setup_sessions(builder)
1762
1842
  return unless sessions?
1843
+
1763
1844
  options = {}
1764
1845
  options[:secret] = session_secret if session_secret?
1765
1846
  options.merge! sessions.to_hash if sessions.respond_to? :to_hash
@@ -1782,15 +1863,15 @@ module Sinatra
1782
1863
  end
1783
1864
 
1784
1865
  # used for deprecation warnings
1785
- def warn(message)
1786
- super message + "\n\tfrom #{cleaned_caller.first.join(':')}"
1866
+ def warn_for_deprecation(message)
1867
+ warn message + "\n\tfrom #{cleaned_caller.first.join(':')}"
1787
1868
  end
1788
1869
 
1789
1870
  # Like Kernel#caller but excluding certain magic entries
1790
1871
  def cleaned_caller(keep = 3)
1791
- caller(1).
1792
- map! { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }.
1793
- reject { |file, *_| callers_to_ignore.any? { |pattern| file =~ pattern } }
1872
+ caller(1)
1873
+ .map! { |line| line.split(/:(?=\d|in )/, 3)[0, keep] }
1874
+ .reject { |file, *_| callers_to_ignore.any? { |pattern| file =~ pattern } }
1794
1875
  end
1795
1876
  end
1796
1877
 
@@ -1798,6 +1879,7 @@ module Sinatra
1798
1879
  # which is UTF-8 by default
1799
1880
  def self.force_encoding(data, encoding = default_encoding)
1800
1881
  return if data == settings || data.is_a?(Tempfile)
1882
+
1801
1883
  if data.respond_to? :force_encoding
1802
1884
  data.force_encoding(encoding).encode!
1803
1885
  elsif data.respond_to? :each_value
@@ -1808,24 +1890,26 @@ module Sinatra
1808
1890
  data
1809
1891
  end
1810
1892
 
1811
- def force_encoding(*args) settings.force_encoding(*args) end
1893
+ def force_encoding(*args)
1894
+ settings.force_encoding(*args)
1895
+ end
1812
1896
 
1813
1897
  reset!
1814
1898
 
1815
1899
  set :environment, (ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym
1816
- set :raise_errors, Proc.new { test? }
1817
- set :dump_errors, Proc.new { !test? }
1818
- set :show_exceptions, Proc.new { development? }
1900
+ set :raise_errors, proc { test? }
1901
+ set :dump_errors, proc { !test? }
1902
+ set :show_exceptions, proc { development? }
1819
1903
  set :sessions, false
1820
- set :session_store, Rack::Session::Cookie
1904
+ set :session_store, Rack::Protection::EncryptedCookie
1821
1905
  set :logging, false
1822
1906
  set :protection, true
1823
1907
  set :method_override, false
1824
1908
  set :use_code, false
1825
- set :default_encoding, "utf-8"
1909
+ set :default_encoding, 'utf-8'
1826
1910
  set :x_cascade, true
1827
1911
  set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" }
1828
- settings.add_charset << /^text\//
1912
+ settings.add_charset << %r{^text/}
1829
1913
  set :mustermann_opts, {}
1830
1914
  set :default_content_type, 'text/html'
1831
1915
 
@@ -1833,14 +1917,15 @@ module Sinatra
1833
1917
  begin
1834
1918
  require 'securerandom'
1835
1919
  set :session_secret, SecureRandom.hex(64)
1836
- rescue LoadError, NotImplementedError
1920
+ rescue LoadError, NotImplementedError, RuntimeError
1837
1921
  # SecureRandom raises a NotImplementedError if no random device is available
1838
- set :session_secret, "%064x" % Kernel.rand(2**256-1)
1922
+ # RuntimeError raised due to broken openssl backend: https://bugs.ruby-lang.org/issues/19230
1923
+ set :session_secret, format('%064x', Kernel.rand((2**256) - 1))
1839
1924
  end
1840
1925
 
1841
1926
  class << self
1842
- alias_method :methodoverride?, :method_override?
1843
- alias_method :methodoverride=, :method_override=
1927
+ alias methodoverride? method_override?
1928
+ alias methodoverride= method_override=
1844
1929
  end
1845
1930
 
1846
1931
  set :run, false # start server via at-exit hook?
@@ -1848,21 +1933,16 @@ module Sinatra
1848
1933
  set :handler_name, nil
1849
1934
  set :traps, true
1850
1935
  set :server, %w[HTTP webrick]
1851
- set :bind, Proc.new { development? ? 'localhost' : '0.0.0.0' }
1936
+ set :bind, proc { development? ? 'localhost' : '0.0.0.0' }
1852
1937
  set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567)
1853
1938
  set :quiet, false
1854
1939
 
1855
1940
  ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
1856
1941
 
1857
- if ruby_engine == 'macruby'
1858
- server.unshift 'control_tower'
1859
- else
1860
- server.unshift 'reel'
1861
- server.unshift 'puma'
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
1942
+ server.unshift 'thin' if ruby_engine != 'jruby'
1943
+ server.unshift 'falcon' if ruby_engine != 'jruby'
1944
+ server.unshift 'trinidad' if ruby_engine == 'jruby'
1945
+ server.unshift 'puma'
1866
1946
 
1867
1947
  set :absolute_redirects, true
1868
1948
  set :prefixed_redirects, false
@@ -1870,14 +1950,14 @@ module Sinatra
1870
1950
  set :strict_paths, true
1871
1951
 
1872
1952
  set :app_file, nil
1873
- set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
1874
- set :views, Proc.new { root && File.join(root, 'views') }
1875
- set :reload_templates, Proc.new { development? }
1953
+ set :root, proc { app_file && File.expand_path(File.dirname(app_file)) }
1954
+ set :views, proc { root && File.join(root, 'views') }
1955
+ set :reload_templates, proc { development? }
1876
1956
  set :lock, false
1877
1957
  set :threaded, true
1878
1958
 
1879
- set :public_folder, Proc.new { root && File.join(root, 'public') }
1880
- set :static, Proc.new { public_folder && File.exist?(public_folder) }
1959
+ set :public_folder, proc { root && File.join(root, 'public') }
1960
+ set :static, proc { public_folder && File.exist?(public_folder) }
1881
1961
  set :static_cache_control, false
1882
1962
 
1883
1963
  error ::Exception do
@@ -1896,7 +1976,7 @@ module Sinatra
1896
1976
  error NotFound do
1897
1977
  content_type 'text/html'
1898
1978
 
1899
- if self.class == Sinatra::Application
1979
+ if instance_of?(Sinatra::Application)
1900
1980
  code = <<-RUBY.gsub(/^ {12}/, '')
1901
1981
  #{request.request_method.downcase} '#{request.path_info}' do
1902
1982
  "Hello World"
@@ -1911,11 +1991,11 @@ module Sinatra
1911
1991
  end
1912
1992
  RUBY
1913
1993
 
1914
- file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(/^\//, '')
1994
+ file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(%r{^/}, '')
1915
1995
  code = "# in #{file}\n#{code}" unless file.empty?
1916
1996
  end
1917
1997
 
1918
- (<<-HTML).gsub(/^ {10}/, '')
1998
+ <<-HTML.gsub(/^ {10}/, '')
1919
1999
  <!DOCTYPE html>
1920
2000
  <html>
1921
2001
  <head>
@@ -1927,7 +2007,7 @@ module Sinatra
1927
2007
  </head>
1928
2008
  <body>
1929
2009
  <h2>Sinatra doesn’t know this ditty.</h2>
1930
- <img src='#{uri "/__sinatra__/404.png"}'>
2010
+ <img src='#{request.script_name}/__sinatra__/404.png'>
1931
2011
  <div id="c">
1932
2012
  Try this:
1933
2013
  <pre>#{Rack::Utils.escape_html(code)}</pre>
@@ -1947,12 +2027,12 @@ module Sinatra
1947
2027
  # top-level. Subclassing Sinatra::Base is highly recommended for
1948
2028
  # modular applications.
1949
2029
  class Application < Base
1950
- set :logging, Proc.new { !test? }
2030
+ set :logging, proc { !test? }
1951
2031
  set :method_override, true
1952
- set :run, Proc.new { !test? }
2032
+ set :run, proc { !test? }
1953
2033
  set :app_file, nil
1954
2034
 
1955
- def self.register(*extensions, &block) #:nodoc:
2035
+ def self.register(*extensions, &block) # :nodoc:
1956
2036
  added_methods = extensions.flat_map(&:public_instance_methods)
1957
2037
  Delegator.delegate(*added_methods)
1958
2038
  super(*extensions, &block)
@@ -1962,11 +2042,12 @@ module Sinatra
1962
2042
  # Sinatra delegation mixin. Mixing this module into an object causes all
1963
2043
  # methods to be delegated to the Sinatra::Application class. Used primarily
1964
2044
  # at the top-level.
1965
- module Delegator #:nodoc:
2045
+ module Delegator # :nodoc:
1966
2046
  def self.delegate(*methods)
1967
2047
  methods.each do |method_name|
1968
2048
  define_method(method_name) do |*args, &block|
1969
2049
  return super(*args, &block) if respond_to? method_name
2050
+
1970
2051
  Delegator.target.send(method_name, *args, &block)
1971
2052
  end
1972
2053
  # ensure keyword argument passing is compatible with ruby >= 2.7
@@ -1978,7 +2059,7 @@ module Sinatra
1978
2059
  delegate :get, :patch, :put, :post, :delete, :head, :options, :link, :unlink,
1979
2060
  :template, :layout, :before, :after, :error, :not_found, :configure,
1980
2061
  :set, :mime_type, :enable, :disable, :use, :development?, :test?,
1981
- :production?, :helpers, :settings, :register
2062
+ :production?, :helpers, :settings, :register, :on_start, :on_stop
1982
2063
 
1983
2064
  class << self
1984
2065
  attr_accessor :target
@@ -1989,7 +2070,8 @@ module Sinatra
1989
2070
 
1990
2071
  class Wrapper
1991
2072
  def initialize(stack, instance)
1992
- @stack, @instance = stack, instance
2073
+ @stack = stack
2074
+ @instance = instance
1993
2075
  end
1994
2076
 
1995
2077
  def settings