sinatra 2.2.3 → 3.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +101 -17
- data/CONTRIBUTING.md +11 -11
- data/Gemfile +44 -64
- data/MAINTENANCE.md +2 -2
- data/README.md +116 -414
- data/Rakefile +66 -75
- data/VERSION +1 -1
- data/examples/chat.rb +25 -12
- data/examples/rainbows.rb +3 -1
- data/examples/simple.rb +2 -0
- data/examples/stream.ru +2 -0
- data/lib/sinatra/base.rb +354 -336
- data/lib/sinatra/indifferent_hash.rb +25 -33
- data/lib/sinatra/main.rb +18 -16
- data/lib/sinatra/show_exceptions.rb +17 -15
- data/lib/sinatra/version.rb +3 -1
- data/lib/sinatra.rb +2 -0
- data/sinatra.gemspec +38 -33
- metadata +34 -26
- data/README.de.md +0 -3239
- data/README.es.md +0 -3231
- data/README.fr.md +0 -3111
- data/README.hu.md +0 -728
- data/README.ja.md +0 -2844
- data/README.ko.md +0 -2967
- data/README.malayalam.md +0 -3141
- data/README.pt-br.md +0 -3787
- data/README.pt-pt.md +0 -791
- data/README.ru.md +0 -3207
- data/README.zh.md +0 -2934
data/lib/sinatra/base.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
# external dependencies
|
@@ -10,7 +9,6 @@ require 'mustermann/sinatra'
|
|
10
9
|
require 'mustermann/regular'
|
11
10
|
|
12
11
|
# stdlib dependencies
|
13
|
-
require 'thread'
|
14
12
|
require 'time'
|
15
13
|
require 'uri'
|
16
14
|
|
@@ -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
|
-
#
|
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 =
|
24
|
+
HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/.freeze
|
25
|
+
HEADER_VALUE_WITH_PARAMS = %r{(?:(?:\w+|\*)/(?:\w+(?:\.|-|\+)?|\*)*)\s*(?:;#{HEADER_PARAM})*}.freeze
|
28
26
|
|
29
27
|
# Returns an array of acceptable media types for the response
|
30
28
|
def accept
|
31
|
-
@env['sinatra.accept'] ||=
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
29
|
+
@env['sinatra.accept'] ||= if @env.include?('HTTP_ACCEPT') && (@env['HTTP_ACCEPT'].to_s != '')
|
30
|
+
@env['HTTP_ACCEPT']
|
31
|
+
.to_s
|
32
|
+
.scan(HEADER_VALUE_WITH_PARAMS)
|
33
|
+
.map! { |e| AcceptEntry.new(e) }
|
34
|
+
.sort
|
35
|
+
else
|
36
|
+
[AcceptEntry.new('*/*')]
|
37
|
+
end
|
39
38
|
end
|
40
39
|
|
41
40
|
def accept?(type)
|
@@ -44,8 +43,10 @@ module Sinatra
|
|
44
43
|
|
45
44
|
def preferred_type(*types)
|
46
45
|
return accept.first if types.empty?
|
46
|
+
|
47
47
|
types.flatten!
|
48
48
|
return types.first if accept.empty?
|
49
|
+
|
49
50
|
accept.detect do |accept_header|
|
50
51
|
type = types.detect { |t| MimeTypeEntry.new(t).accepts?(accept_header) }
|
51
52
|
return type if type
|
@@ -55,23 +56,23 @@ module Sinatra
|
|
55
56
|
alias secure? ssl?
|
56
57
|
|
57
58
|
def forwarded?
|
58
|
-
@env.include?
|
59
|
+
@env.include? 'HTTP_X_FORWARDED_HOST'
|
59
60
|
end
|
60
61
|
|
61
62
|
def safe?
|
62
|
-
get?
|
63
|
+
get? || head? || options? || trace?
|
63
64
|
end
|
64
65
|
|
65
66
|
def idempotent?
|
66
|
-
safe?
|
67
|
+
safe? || put? || delete? || link? || unlink?
|
67
68
|
end
|
68
69
|
|
69
70
|
def link?
|
70
|
-
request_method ==
|
71
|
+
request_method == 'LINK'
|
71
72
|
end
|
72
73
|
|
73
74
|
def unlink?
|
74
|
-
request_method ==
|
75
|
+
request_method == 'UNLINK'
|
75
76
|
end
|
76
77
|
|
77
78
|
def params
|
@@ -95,17 +96,17 @@ module Sinatra
|
|
95
96
|
|
96
97
|
@entry = entry
|
97
98
|
@type = entry[/[^;]+/].delete(' ')
|
98
|
-
@params =
|
99
|
+
@params = params.to_h
|
99
100
|
@q = @params.delete('q') { 1.0 }.to_f
|
100
101
|
end
|
101
102
|
|
102
103
|
def <=>(other)
|
103
|
-
other.priority <=>
|
104
|
+
other.priority <=> priority
|
104
105
|
end
|
105
106
|
|
106
107
|
def priority
|
107
108
|
# We sort in descending order; better matches should be higher.
|
108
|
-
[
|
109
|
+
[@q, -@type.count('*'), @params.size]
|
109
110
|
end
|
110
111
|
|
111
112
|
def to_str
|
@@ -117,7 +118,7 @@ module Sinatra
|
|
117
118
|
end
|
118
119
|
|
119
120
|
def respond_to?(*args)
|
120
|
-
super
|
121
|
+
super || to_str.respond_to?(*args)
|
121
122
|
end
|
122
123
|
|
123
124
|
def method_missing(*args, &block)
|
@@ -136,7 +137,7 @@ module Sinatra
|
|
136
137
|
end
|
137
138
|
|
138
139
|
@type = entry[/[^;]+/].delete(' ')
|
139
|
-
@params =
|
140
|
+
@params = params.to_h
|
140
141
|
end
|
141
142
|
|
142
143
|
def accepts?(entry)
|
@@ -150,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.
|
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
|
-
#
|
161
|
-
#
|
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
|
179
|
-
headers.delete
|
179
|
+
headers.delete 'Content-Length'
|
180
|
+
headers.delete 'Content-Type'
|
180
181
|
end
|
181
182
|
|
182
183
|
if drop_body?
|
@@ -187,7 +188,7 @@ module Sinatra
|
|
187
188
|
if calculate_content_length?
|
188
189
|
# if some other code has already set Content-Length, don't muck with it
|
189
190
|
# currently, this would be the static file-handler
|
190
|
-
headers[
|
191
|
+
headers['Content-Length'] = body.map(&:bytesize).reduce(0, :+).to_s
|
191
192
|
end
|
192
193
|
|
193
194
|
[status, headers, result]
|
@@ -196,11 +197,11 @@ module Sinatra
|
|
196
197
|
private
|
197
198
|
|
198
199
|
def calculate_content_length?
|
199
|
-
headers[
|
200
|
+
headers['Content-Type'] && !headers['Content-Length'] && (Array === body)
|
200
201
|
end
|
201
202
|
|
202
203
|
def drop_content_info?
|
203
|
-
informational?
|
204
|
+
informational? || drop_body?
|
204
205
|
end
|
205
206
|
|
206
207
|
def drop_body?
|
@@ -215,8 +216,10 @@ module Sinatra
|
|
215
216
|
# still be able to run.
|
216
217
|
class ExtendedRack < Struct.new(:app)
|
217
218
|
def call(env)
|
218
|
-
result
|
219
|
-
|
219
|
+
result = app.call(env)
|
220
|
+
callback = env['async.callback']
|
221
|
+
return result unless callback && async?(*result)
|
222
|
+
|
220
223
|
after_response { callback.call result }
|
221
224
|
setup_close(env, *result)
|
222
225
|
throw :async
|
@@ -224,20 +227,23 @@ module Sinatra
|
|
224
227
|
|
225
228
|
private
|
226
229
|
|
227
|
-
def setup_close(env,
|
228
|
-
return unless body.respond_to?
|
230
|
+
def setup_close(env, _status, _headers, body)
|
231
|
+
return unless body.respond_to?(:close) && env.include?('async.close')
|
232
|
+
|
229
233
|
env['async.close'].callback { body.close }
|
230
234
|
env['async.close'].errback { body.close }
|
231
235
|
end
|
232
236
|
|
233
237
|
def after_response(&block)
|
234
|
-
raise NotImplementedError,
|
238
|
+
raise NotImplementedError, 'only supports EventMachine at the moment' unless defined? EventMachine
|
239
|
+
|
235
240
|
EventMachine.next_tick(&block)
|
236
241
|
end
|
237
242
|
|
238
|
-
def async?(status,
|
243
|
+
def async?(status, _headers, body)
|
239
244
|
return true if status == -1
|
240
|
-
|
245
|
+
|
246
|
+
body.respond_to?(:callback) && body.respond_to?(:errback)
|
241
247
|
end
|
242
248
|
end
|
243
249
|
|
@@ -249,7 +255,7 @@ module Sinatra
|
|
249
255
|
end
|
250
256
|
|
251
257
|
superclass.class_eval do
|
252
|
-
|
258
|
+
alias_method :call_without_check, :call unless method_defined? :call_without_check
|
253
259
|
def call(env)
|
254
260
|
env['sinatra.commonlogger'] = true
|
255
261
|
call_without_check(env)
|
@@ -257,11 +263,14 @@ module Sinatra
|
|
257
263
|
end
|
258
264
|
end
|
259
265
|
|
260
|
-
class
|
266
|
+
class Error < StandardError # :nodoc:
|
267
|
+
end
|
268
|
+
|
269
|
+
class BadRequest < Error # :nodoc:
|
261
270
|
def http_status; 400 end
|
262
271
|
end
|
263
272
|
|
264
|
-
class NotFound <
|
273
|
+
class NotFound < Error # :nodoc:
|
265
274
|
def http_status; 404 end
|
266
275
|
end
|
267
276
|
|
@@ -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::
|
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'
|
305
|
+
if (env['HTTP_VERSION'] == 'HTTP/1.1') && (env['REQUEST_METHOD'] != 'GET')
|
297
306
|
status 303
|
298
307
|
else
|
299
308
|
status 302
|
@@ -308,18 +317,19 @@ module Sinatra
|
|
308
317
|
# Generates the absolute URI for a given path in the app.
|
309
318
|
# Takes Rack routers and reverse proxies into account.
|
310
319
|
def uri(addr = nil, absolute = true, add_script_name = true)
|
311
|
-
return addr if addr =~ /\A[a-z][a-z0-9
|
320
|
+
return addr if addr.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?
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
325
|
+
host << if request.forwarded? || (request.port != (request.secure? ? 443 : 80))
|
326
|
+
request.host_with_port
|
327
|
+
else
|
328
|
+
request.host
|
329
|
+
end
|
320
330
|
end
|
321
331
|
uri << request.script_name.to_s if add_script_name
|
322
|
-
uri << (addr
|
332
|
+
uri << (addr || request.path_info).to_s
|
323
333
|
File.join uri
|
324
334
|
end
|
325
335
|
|
@@ -328,7 +338,10 @@ module Sinatra
|
|
328
338
|
|
329
339
|
# Halt processing and return the error status provided.
|
330
340
|
def error(code, body = nil)
|
331
|
-
|
341
|
+
if code.respond_to? :to_str
|
342
|
+
body = code.to_str
|
343
|
+
code = 500
|
344
|
+
end
|
332
345
|
response.body = body unless body.nil?
|
333
346
|
halt code
|
334
347
|
end
|
@@ -363,11 +376,13 @@ module Sinatra
|
|
363
376
|
# extension.
|
364
377
|
def content_type(type = nil, params = {})
|
365
378
|
return response['Content-Type'] unless type
|
379
|
+
|
366
380
|
default = params.delete :default
|
367
381
|
mime_type = mime_type(type) || default
|
368
|
-
|
382
|
+
raise format('Unknown media type: %p', type) if mime_type.nil?
|
383
|
+
|
369
384
|
mime_type = mime_type.dup
|
370
|
-
unless params.include?
|
385
|
+
unless params.include?(:charset) || settings.add_charset.all? { |p| !(p === mime_type) }
|
371
386
|
params[:charset] = params.delete('charset') || settings.default_encoding
|
372
387
|
end
|
373
388
|
params.delete :charset if mime_type.include? 'charset'
|
@@ -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]
|
406
|
-
content_type opts[:type] || File.extname(path), :
|
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?
|
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::
|
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
|
442
|
-
@
|
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
|
|
@@ -486,7 +507,16 @@ module Sinatra
|
|
486
507
|
def stream(keep_open = false)
|
487
508
|
scheduler = env['async.callback'] ? EventMachine : Stream
|
488
509
|
current = @params.dup
|
489
|
-
|
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
|
490
520
|
end
|
491
521
|
|
492
522
|
# Specify response freshness policy for HTTP caches (Cache-Control header).
|
@@ -500,18 +530,18 @@ module Sinatra
|
|
500
530
|
# See RFC 2616 / 14.9 for more on standard cache control directives:
|
501
531
|
# http://tools.ietf.org/html/rfc2616#section-14.9.1
|
502
532
|
def cache_control(*values)
|
503
|
-
if values.last.
|
533
|
+
if values.last.is_a?(Hash)
|
504
534
|
hash = values.pop
|
505
|
-
hash.reject! { |
|
535
|
+
hash.reject! { |_k, v| v == false }
|
506
536
|
hash.reject! { |k, v| values << k if v == true }
|
507
537
|
else
|
508
538
|
hash = {}
|
509
539
|
end
|
510
540
|
|
511
|
-
values.map! { |value| value.to_s.tr('_','-') }
|
541
|
+
values.map! { |value| value.to_s.tr('_', '-') }
|
512
542
|
hash.each do |key, value|
|
513
543
|
key = key.to_s.tr('_', '-')
|
514
|
-
value = value.to_i if [
|
544
|
+
value = value.to_i if %w[max-age s-maxage].include? key
|
515
545
|
values << "#{key}=#{value}"
|
516
546
|
end
|
517
547
|
|
@@ -528,7 +558,7 @@ module Sinatra
|
|
528
558
|
# => Expires: Mon, 08 Jun 2009 08:50:17 GMT
|
529
559
|
#
|
530
560
|
def expires(amount, *values)
|
531
|
-
values << {} unless values.last.
|
561
|
+
values << {} unless values.last.is_a?(Hash)
|
532
562
|
|
533
563
|
if amount.is_a? Integer
|
534
564
|
time = Time.now + amount.to_i
|
@@ -538,7 +568,7 @@ module Sinatra
|
|
538
568
|
max_age = time - Time.now
|
539
569
|
end
|
540
570
|
|
541
|
-
values.last.merge!(:max_age
|
571
|
+
values.last.merge!(max_age: max_age) { |_key, v1, v2| v1 || v2 }
|
542
572
|
cache_control(*values)
|
543
573
|
|
544
574
|
response['Expires'] = time.httpdate
|
@@ -553,17 +583,18 @@ module Sinatra
|
|
553
583
|
# with a '304 Not Modified' response.
|
554
584
|
def last_modified(time)
|
555
585
|
return unless time
|
586
|
+
|
556
587
|
time = time_for time
|
557
588
|
response['Last-Modified'] = time.httpdate
|
558
589
|
return if env['HTTP_IF_NONE_MATCH']
|
559
590
|
|
560
|
-
if status == 200
|
591
|
+
if (status == 200) && env['HTTP_IF_MODIFIED_SINCE']
|
561
592
|
# compare based on seconds since epoch
|
562
593
|
since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
|
563
594
|
halt 304 if since >= time.to_i
|
564
595
|
end
|
565
596
|
|
566
|
-
if (success?
|
597
|
+
if (success? || (status == 412)) && env['HTTP_IF_UNMODIFIED_SINCE']
|
567
598
|
# compare based on seconds since epoch
|
568
599
|
since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
|
569
600
|
halt 412 if since < time.to_i
|
@@ -571,7 +602,7 @@ module Sinatra
|
|
571
602
|
rescue ArgumentError
|
572
603
|
end
|
573
604
|
|
574
|
-
ETAG_KINDS = [
|
605
|
+
ETAG_KINDS = %i[strong weak].freeze
|
575
606
|
# Set the response entity tag (HTTP 'ETag' header) and halt if conditional
|
576
607
|
# GET matches. The +value+ argument is an identifier that uniquely
|
577
608
|
# identifies the current version of the resource. The +kind+ argument
|
@@ -583,27 +614,31 @@ module Sinatra
|
|
583
614
|
# GET or HEAD, a '304 Not Modified' response is sent.
|
584
615
|
def etag(value, options = {})
|
585
616
|
# Before touching this code, please double check RFC 2616 14.24 and 14.26.
|
586
|
-
options = {:
|
617
|
+
options = { kind: options } unless Hash === options
|
587
618
|
kind = options[:kind] || :strong
|
588
619
|
new_resource = options.fetch(:new_resource) { request.post? }
|
589
620
|
|
590
621
|
unless ETAG_KINDS.include?(kind)
|
591
|
-
raise ArgumentError,
|
622
|
+
raise ArgumentError, ':strong or :weak expected'
|
592
623
|
end
|
593
624
|
|
594
|
-
value = '"%s"'
|
625
|
+
value = format('"%s"', value)
|
595
626
|
value = "W/#{value}" if kind == :weak
|
596
627
|
response['ETag'] = value
|
597
628
|
|
598
|
-
|
599
|
-
if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
|
600
|
-
halt(request.safe? ? 304 : 412)
|
601
|
-
end
|
629
|
+
return unless success? || status == 304
|
602
630
|
|
603
|
-
|
604
|
-
|
605
|
-
|
631
|
+
if etag_matches?(env['HTTP_IF_NONE_MATCH'], new_resource)
|
632
|
+
halt(request.safe? ? 304 : 412)
|
633
|
+
end
|
634
|
+
|
635
|
+
if env['HTTP_IF_MATCH']
|
636
|
+
return if etag_matches?(env['HTTP_IF_MATCH'], new_resource)
|
637
|
+
|
638
|
+
halt 412
|
606
639
|
end
|
640
|
+
|
641
|
+
nil
|
607
642
|
end
|
608
643
|
|
609
644
|
# Sugar for redirect (example: redirect back)
|
@@ -656,8 +691,8 @@ module Sinatra
|
|
656
691
|
else
|
657
692
|
value.to_time
|
658
693
|
end
|
659
|
-
rescue ArgumentError =>
|
660
|
-
raise
|
694
|
+
rescue ArgumentError => e
|
695
|
+
raise e
|
661
696
|
rescue Exception
|
662
697
|
raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
|
663
698
|
end
|
@@ -667,11 +702,13 @@ module Sinatra
|
|
667
702
|
# Helper method checking if a ETag value list includes the current ETag.
|
668
703
|
def etag_matches?(list, new_resource = request.post?)
|
669
704
|
return !new_resource if list == '*'
|
705
|
+
|
670
706
|
list.to_s.split(/\s*,\s*/).include? response['ETag']
|
671
707
|
end
|
672
708
|
|
673
709
|
def with_params(temp_params)
|
674
|
-
original
|
710
|
+
original = @params
|
711
|
+
@params = temp_params
|
675
712
|
yield
|
676
713
|
ensure
|
677
714
|
@params = original if original
|
@@ -689,7 +726,7 @@ module Sinatra
|
|
689
726
|
# Possible options are:
|
690
727
|
# :content_type The content type to use, same arguments as content_type.
|
691
728
|
# :layout If set to something falsy, no layout is rendered, otherwise
|
692
|
-
# the specified layout is used
|
729
|
+
# the specified layout is used
|
693
730
|
# :layout_engine Engine to use for rendering the layout.
|
694
731
|
# :locals A hash with local variables that should be available
|
695
732
|
# in the template
|
@@ -711,36 +748,10 @@ module Sinatra
|
|
711
748
|
render(:erb, template, options, locals, &block)
|
712
749
|
end
|
713
750
|
|
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
751
|
def haml(template, options = {}, locals = {}, &block)
|
721
752
|
render(:haml, template, options, locals, &block)
|
722
753
|
end
|
723
754
|
|
724
|
-
def sass(template, options = {}, locals = {})
|
725
|
-
options.merge! :layout => false, :default_content_type => :css
|
726
|
-
render :sass, template, options, locals
|
727
|
-
end
|
728
|
-
|
729
|
-
def scss(template, options = {}, locals = {})
|
730
|
-
options.merge! :layout => false, :default_content_type => :css
|
731
|
-
render :scss, template, options, locals
|
732
|
-
end
|
733
|
-
|
734
|
-
def less(template, options = {}, locals = {})
|
735
|
-
options.merge! :layout => false, :default_content_type => :css
|
736
|
-
render :less, template, options, locals
|
737
|
-
end
|
738
|
-
|
739
|
-
def stylus(template, options = {}, locals = {})
|
740
|
-
options.merge! :layout => false, :default_content_type => :css
|
741
|
-
render :styl, template, options, locals
|
742
|
-
end
|
743
|
-
|
744
755
|
def builder(template = nil, options = {}, locals = {}, &block)
|
745
756
|
options[:default_content_type] = :xml
|
746
757
|
render_ruby(:builder, template, options, locals, &block)
|
@@ -755,10 +766,6 @@ module Sinatra
|
|
755
766
|
render :markdown, template, options, locals
|
756
767
|
end
|
757
768
|
|
758
|
-
def textile(template, options = {}, locals = {})
|
759
|
-
render :textile, template, options, locals
|
760
|
-
end
|
761
|
-
|
762
769
|
def rdoc(template, options = {}, locals = {})
|
763
770
|
render :rdoc, template, options, locals
|
764
771
|
end
|
@@ -767,19 +774,10 @@ module Sinatra
|
|
767
774
|
render :asciidoc, template, options, locals
|
768
775
|
end
|
769
776
|
|
770
|
-
def radius(template, options = {}, locals = {})
|
771
|
-
render :radius, template, options, locals
|
772
|
-
end
|
773
|
-
|
774
777
|
def markaby(template = nil, options = {}, locals = {}, &block)
|
775
778
|
render_ruby(:mab, template, options, locals, &block)
|
776
779
|
end
|
777
780
|
|
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
781
|
def nokogiri(template = nil, options = {}, locals = {}, &block)
|
784
782
|
options[:default_content_type] = :xml
|
785
783
|
render_ruby(:nokogiri, template, options, locals, &block)
|
@@ -789,18 +787,6 @@ module Sinatra
|
|
789
787
|
render(:slim, template, options, locals, &block)
|
790
788
|
end
|
791
789
|
|
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
790
|
def yajl(template, options = {}, locals = {})
|
805
791
|
options[:default_content_type] = :json
|
806
792
|
render :yajl, template, options, locals
|
@@ -825,24 +811,27 @@ module Sinatra
|
|
825
811
|
|
826
812
|
# logic shared between builder and nokogiri
|
827
813
|
def render_ruby(engine, template, options = {}, locals = {}, &block)
|
828
|
-
|
829
|
-
|
814
|
+
if template.is_a?(Hash)
|
815
|
+
options = template
|
816
|
+
template = nil
|
817
|
+
end
|
818
|
+
template = proc { block } if template.nil?
|
830
819
|
render engine, template, options, locals
|
831
820
|
end
|
832
821
|
|
833
822
|
def render(engine, data, options = {}, locals = {}, &block)
|
834
823
|
# merge app-level options
|
835
824
|
engine_options = settings.respond_to?(engine) ? settings.send(engine) : {}
|
836
|
-
options.merge!(engine_options) { |
|
825
|
+
options.merge!(engine_options) { |_key, v1, _v2| v1 }
|
837
826
|
|
838
827
|
# extract generic options
|
839
828
|
locals = options.delete(:locals) || locals || {}
|
840
|
-
views = options.delete(:views) || settings.views ||
|
829
|
+
views = options.delete(:views) || settings.views || './views'
|
841
830
|
layout = options[:layout]
|
842
831
|
layout = false if layout.nil? && options.include?(:layout)
|
843
832
|
eat_errors = layout.nil?
|
844
|
-
layout = engine_options[:layout] if layout.nil?
|
845
|
-
layout = @default_layout if layout.nil?
|
833
|
+
layout = engine_options[:layout] if layout.nil? || (layout == true && engine_options[:layout] != false)
|
834
|
+
layout = @default_layout if layout.nil? || (layout == true)
|
846
835
|
layout_options = options.delete(:layout_options) || {}
|
847
836
|
content_type = options.delete(:default_content_type)
|
848
837
|
content_type = options.delete(:content_type) || content_type
|
@@ -867,8 +856,9 @@ module Sinatra
|
|
867
856
|
|
868
857
|
# render layout
|
869
858
|
if layout
|
870
|
-
|
871
|
-
|
859
|
+
extra_options = { views: views, layout: false, eat_errors: eat_errors, scope: scope }
|
860
|
+
options = options.merge(extra_options).merge!(layout_options)
|
861
|
+
|
872
862
|
catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } }
|
873
863
|
end
|
874
864
|
|
@@ -893,12 +883,13 @@ module Sinatra
|
|
893
883
|
@preferred_extension = engine.to_s
|
894
884
|
find_template(views, data, template) do |file|
|
895
885
|
path ||= file # keep the initial path rather than the last one
|
896
|
-
|
886
|
+
found = File.exist?(file)
|
887
|
+
if found
|
897
888
|
path = file
|
898
889
|
break
|
899
890
|
end
|
900
891
|
end
|
901
|
-
throw :layout_missing if eat_errors
|
892
|
+
throw :layout_missing if eat_errors && !found
|
902
893
|
template.new(path, 1, options)
|
903
894
|
end
|
904
895
|
end
|
@@ -914,9 +905,11 @@ module Sinatra
|
|
914
905
|
end
|
915
906
|
|
916
907
|
def compile_block_template(template, options, &body)
|
917
|
-
|
918
|
-
path =
|
919
|
-
line =
|
908
|
+
first_location = caller_locations.first
|
909
|
+
path = first_location.path
|
910
|
+
line = first_location.lineno
|
911
|
+
path = options[:path] || path
|
912
|
+
line = options[:line] || line
|
920
913
|
template.new(path, line.to_i, options, &body)
|
921
914
|
end
|
922
915
|
end
|
@@ -932,7 +925,7 @@ module Sinatra
|
|
932
925
|
attr_accessor :app, :env, :request, :response, :params
|
933
926
|
attr_reader :template_cache
|
934
927
|
|
935
|
-
def initialize(app = nil, **
|
928
|
+
def initialize(app = nil, **_kwargs)
|
936
929
|
super()
|
937
930
|
@app = app
|
938
931
|
@template_cache = Tilt::Cache.new
|
@@ -959,7 +952,7 @@ module Sinatra
|
|
959
952
|
unless @response['Content-Type']
|
960
953
|
if Array === body && body[0].respond_to?(:content_type)
|
961
954
|
content_type body[0].content_type
|
962
|
-
elsif default = settings.default_content_type
|
955
|
+
elsif (default = settings.default_content_type)
|
963
956
|
content_type default
|
964
957
|
end
|
965
958
|
end
|
@@ -977,12 +970,6 @@ module Sinatra
|
|
977
970
|
self.class.settings
|
978
971
|
end
|
979
972
|
|
980
|
-
def options
|
981
|
-
warn "Sinatra::Base#options is deprecated and will be removed, " \
|
982
|
-
"use #settings instead."
|
983
|
-
settings
|
984
|
-
end
|
985
|
-
|
986
973
|
# Exit the current block, halts any further processing
|
987
974
|
# of the request, and returns the specified response.
|
988
975
|
def halt(*response)
|
@@ -999,7 +986,8 @@ module Sinatra
|
|
999
986
|
|
1000
987
|
# Forward the request to the downstream app -- middleware only.
|
1001
988
|
def forward
|
1002
|
-
|
989
|
+
raise 'downstream app not set' unless @app.respond_to? :call
|
990
|
+
|
1003
991
|
status, headers, body = @app.call env
|
1004
992
|
@response.status = status
|
1005
993
|
@response.body = body
|
@@ -1021,18 +1009,18 @@ module Sinatra
|
|
1021
1009
|
|
1022
1010
|
# Run routes defined on the class and all superclasses.
|
1023
1011
|
def route!(base = settings, pass_block = nil)
|
1024
|
-
|
1025
|
-
routes.each do |pattern, conditions, block|
|
1026
|
-
response.delete_header('Content-Type') unless @pinned_response
|
1012
|
+
routes = base.routes[@request.request_method]
|
1027
1013
|
|
1028
|
-
|
1029
|
-
|
1030
|
-
route_eval { block[*args] }
|
1031
|
-
end
|
1014
|
+
routes&.each do |pattern, conditions, block|
|
1015
|
+
response.delete_header('Content-Type') unless @pinned_response
|
1032
1016
|
|
1033
|
-
|
1034
|
-
|
1017
|
+
returned_pass_block = process_route(pattern, conditions) do |*args|
|
1018
|
+
env['sinatra.route'] = "#{@request.request_method} #{pattern}"
|
1019
|
+
route_eval { block[*args] }
|
1035
1020
|
end
|
1021
|
+
|
1022
|
+
# don't wipe out pass_block in superclass
|
1023
|
+
pass_block = returned_pass_block if returned_pass_block
|
1036
1024
|
end
|
1037
1025
|
|
1038
1026
|
# Run routes defined in superclass.
|
@@ -1056,15 +1044,17 @@ module Sinatra
|
|
1056
1044
|
# Returns pass block.
|
1057
1045
|
def process_route(pattern, conditions, block = nil, values = [])
|
1058
1046
|
route = @request.path_info
|
1059
|
-
route = '/' if route.empty?
|
1047
|
+
route = '/' if route.empty? && !settings.empty_path_info?
|
1060
1048
|
route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/')
|
1061
|
-
return unless params = pattern.params(route)
|
1062
1049
|
|
1063
|
-
params.
|
1050
|
+
params = pattern.params(route)
|
1051
|
+
return unless params
|
1052
|
+
|
1053
|
+
params.delete('ignore') # TODO: better params handling, maybe turn it into "smart" object or detect changes
|
1064
1054
|
force_encoding(params)
|
1065
|
-
@params = @params.merge(params) if params.any?
|
1055
|
+
@params = @params.merge(params) { |_k, v1, v2| v2 || v1 } if params.any?
|
1066
1056
|
|
1067
|
-
regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? {|subpattern| subpattern.is_a?(Mustermann::Regular)}
|
1057
|
+
regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? { |subpattern| subpattern.is_a?(Mustermann::Regular) })
|
1068
1058
|
if regexp_exists
|
1069
1059
|
captures = pattern.match(route).captures.map { |c| URI_INSTANCE.unescape(c) if c }
|
1070
1060
|
values += captures
|
@@ -1077,7 +1067,7 @@ module Sinatra
|
|
1077
1067
|
conditions.each { |c| throw :pass if c.bind(self).call == false }
|
1078
1068
|
block ? block[self, values] : yield(self, values)
|
1079
1069
|
end
|
1080
|
-
rescue
|
1070
|
+
rescue StandardError
|
1081
1071
|
@env['sinatra.error.params'] = @params
|
1082
1072
|
raise
|
1083
1073
|
ensure
|
@@ -1091,35 +1081,35 @@ module Sinatra
|
|
1091
1081
|
# a NotFound exception. Subclasses can override this method to perform
|
1092
1082
|
# custom route miss logic.
|
1093
1083
|
def route_missing
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
raise NotFound, "#{request.request_method} #{request.path_info}"
|
1098
|
-
end
|
1084
|
+
raise NotFound unless @app
|
1085
|
+
|
1086
|
+
forward
|
1099
1087
|
end
|
1100
1088
|
|
1101
1089
|
# Attempt to serve static files from public directory. Throws :halt when
|
1102
1090
|
# a matching file is found, returns nil otherwise.
|
1103
1091
|
def static!(options = {})
|
1104
1092
|
return if (public_dir = settings.public_folder).nil?
|
1093
|
+
|
1105
1094
|
path = "#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}"
|
1106
1095
|
return unless valid_path?(path)
|
1107
1096
|
|
1108
1097
|
path = File.expand_path(path)
|
1109
|
-
return unless path.start_with?(File.expand_path(public_dir)
|
1098
|
+
return unless path.start_with?("#{File.expand_path(public_dir)}/")
|
1099
|
+
|
1110
1100
|
return unless File.file?(path)
|
1111
1101
|
|
1112
1102
|
env['sinatra.static_file'] = path
|
1113
1103
|
cache_control(*settings.static_cache_control) if settings.static_cache_control?
|
1114
|
-
send_file path, options.merge(:
|
1104
|
+
send_file path, options.merge(disposition: nil)
|
1115
1105
|
end
|
1116
1106
|
|
1117
1107
|
# Run the block with 'throw :halt' support and apply result to the response.
|
1118
|
-
def invoke
|
1119
|
-
res = catch(:halt)
|
1108
|
+
def invoke(&block)
|
1109
|
+
res = catch(:halt, &block)
|
1120
1110
|
|
1121
|
-
res = [res] if Integer === res
|
1122
|
-
if Array === res
|
1111
|
+
res = [res] if (Integer === res) || (String === res)
|
1112
|
+
if (Array === res) && (Integer === res.first)
|
1123
1113
|
res = res.dup
|
1124
1114
|
status(res.shift)
|
1125
1115
|
body(res.pop)
|
@@ -1135,6 +1125,7 @@ module Sinatra
|
|
1135
1125
|
# Avoid passing frozen string in force_encoding
|
1136
1126
|
@params.merge!(@request.params).each do |key, val|
|
1137
1127
|
next unless val.respond_to?(:force_encoding)
|
1128
|
+
|
1138
1129
|
val = val.dup if val.frozen?
|
1139
1130
|
@params[key] = force_encoding(val)
|
1140
1131
|
end
|
@@ -1146,39 +1137,43 @@ module Sinatra
|
|
1146
1137
|
end
|
1147
1138
|
route!
|
1148
1139
|
end
|
1149
|
-
rescue ::Exception =>
|
1150
|
-
invoke { handle_exception!(
|
1140
|
+
rescue ::Exception => e
|
1141
|
+
invoke { handle_exception!(e) }
|
1151
1142
|
ensure
|
1152
1143
|
begin
|
1153
1144
|
filter! :after unless env['sinatra.static_file']
|
1154
|
-
rescue ::Exception =>
|
1155
|
-
invoke { handle_exception!(
|
1145
|
+
rescue ::Exception => e
|
1146
|
+
invoke { handle_exception!(e) } unless @env['sinatra.error']
|
1156
1147
|
end
|
1157
1148
|
end
|
1158
1149
|
|
1159
1150
|
# Error handling during requests.
|
1160
1151
|
def handle_exception!(boom)
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1152
|
+
error_params = @env['sinatra.error.params']
|
1153
|
+
|
1154
|
+
@params = @params.merge(error_params) if error_params
|
1155
|
+
|
1164
1156
|
@env['sinatra.error'] = boom
|
1165
1157
|
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1158
|
+
http_status = if boom.is_a? Sinatra::Error
|
1159
|
+
if boom.respond_to? :http_status
|
1160
|
+
boom.http_status
|
1161
|
+
elsif settings.use_code? && boom.respond_to?(:code)
|
1162
|
+
boom.code
|
1163
|
+
end
|
1164
|
+
end
|
1165
|
+
|
1166
|
+
http_status = 500 unless http_status&.between?(400, 599)
|
1167
|
+
status(http_status)
|
1173
1168
|
|
1174
1169
|
if server_error?
|
1175
1170
|
dump_errors! boom if settings.dump_errors?
|
1176
|
-
raise boom if settings.show_exceptions?
|
1171
|
+
raise boom if settings.show_exceptions? && (settings.show_exceptions != :after_handler)
|
1177
1172
|
elsif not_found?
|
1178
1173
|
headers['X-Cascade'] = 'pass' if settings.x_cascade?
|
1179
1174
|
end
|
1180
1175
|
|
1181
|
-
if res = error_block!(boom.class, boom) || error_block!(status, boom)
|
1176
|
+
if (res = error_block!(boom.class, boom) || error_block!(status, boom))
|
1182
1177
|
return res
|
1183
1178
|
end
|
1184
1179
|
|
@@ -1187,12 +1182,14 @@ module Sinatra
|
|
1187
1182
|
body Rack::Utils.escape_html(boom.message)
|
1188
1183
|
else
|
1189
1184
|
content_type 'text/html'
|
1190
|
-
body
|
1185
|
+
body "<h1>#{not_found? ? 'Not Found' : 'Bad Request'}</h1>"
|
1191
1186
|
end
|
1192
1187
|
end
|
1193
1188
|
|
1194
1189
|
return unless server_error?
|
1195
|
-
|
1190
|
+
|
1191
|
+
raise boom if settings.raise_errors? || settings.show_exceptions?
|
1192
|
+
|
1196
1193
|
error_block! Exception, boom
|
1197
1194
|
end
|
1198
1195
|
|
@@ -1200,7 +1197,10 @@ module Sinatra
|
|
1200
1197
|
def error_block!(key, *block_params)
|
1201
1198
|
base = settings
|
1202
1199
|
while base.respond_to?(:errors)
|
1203
|
-
|
1200
|
+
args_array = base.errors[key]
|
1201
|
+
|
1202
|
+
next base = base.superclass unless args_array
|
1203
|
+
|
1204
1204
|
args_array.reverse_each do |args|
|
1205
1205
|
first = args == args_array.first
|
1206
1206
|
args += [block_params]
|
@@ -1208,51 +1208,50 @@ module Sinatra
|
|
1208
1208
|
return resp unless resp.nil? && !first
|
1209
1209
|
end
|
1210
1210
|
end
|
1211
|
-
return false unless key.respond_to?
|
1211
|
+
return false unless key.respond_to?(:superclass) && (key.superclass < Exception)
|
1212
|
+
|
1212
1213
|
error_block!(key.superclass, *block_params)
|
1213
1214
|
end
|
1214
1215
|
|
1215
1216
|
def dump_errors!(boom)
|
1216
|
-
msg = ["#{Time.now.strftime(
|
1217
|
+
msg = ["#{Time.now.strftime('%Y-%m-%d %H:%M:%S')} - #{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t")
|
1217
1218
|
@env['rack.errors'].puts(msg)
|
1218
1219
|
end
|
1219
1220
|
|
1220
1221
|
class << self
|
1221
1222
|
CALLERS_TO_IGNORE = [ # :nodoc:
|
1222
|
-
|
1223
|
-
/
|
1223
|
+
%r{/sinatra(/(base|main|show_exceptions))?\.rb$}, # all sinatra code
|
1224
|
+
%r{lib/tilt.*\.rb$}, # all tilt code
|
1224
1225
|
/^\(.*\)$/, # generated code
|
1225
|
-
/
|
1226
|
+
%r{rubygems/(custom|core_ext/kernel)_require\.rb$}, # rubygems require hacks
|
1226
1227
|
/active_support/, # active_support require hacks
|
1227
|
-
|
1228
|
+
%r{bundler(/(?:runtime|inline))?\.rb}, # bundler require hacks
|
1228
1229
|
/<internal:/, # internal in ruby >= 1.9.2
|
1229
|
-
/
|
1230
|
-
]
|
1231
|
-
|
1232
|
-
# contrary to what the comment said previously, rubinius never supported this
|
1233
|
-
if defined?(RUBY_IGNORE_CALLERS)
|
1234
|
-
warn "RUBY_IGNORE_CALLERS is deprecated and will no longer be supported by Sinatra 2.0"
|
1235
|
-
CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS)
|
1236
|
-
end
|
1230
|
+
%r{zeitwerk/kernel\.rb} # Zeitwerk kernel#require decorator
|
1231
|
+
].freeze
|
1237
1232
|
|
1238
1233
|
attr_reader :routes, :filters, :templates, :errors
|
1239
1234
|
|
1235
|
+
def callers_to_ignore
|
1236
|
+
CALLERS_TO_IGNORE
|
1237
|
+
end
|
1238
|
+
|
1240
1239
|
# Removes all routes, filters, middleware and extension hooks from the
|
1241
1240
|
# current class (not routes/filters/... defined by its superclass).
|
1242
1241
|
def reset!
|
1243
1242
|
@conditions = []
|
1244
1243
|
@routes = {}
|
1245
|
-
@filters = {:
|
1244
|
+
@filters = { before: [], after: [] }
|
1246
1245
|
@errors = {}
|
1247
1246
|
@middleware = []
|
1248
1247
|
@prototype = nil
|
1249
1248
|
@extensions = []
|
1250
1249
|
|
1251
|
-
if superclass.respond_to?(:templates)
|
1252
|
-
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1250
|
+
@templates = if superclass.respond_to?(:templates)
|
1251
|
+
Hash.new { |_hash, key| superclass.templates[key] }
|
1252
|
+
else
|
1253
|
+
{}
|
1254
|
+
end
|
1256
1255
|
end
|
1257
1256
|
|
1258
1257
|
# Extension modules registered on this class and all superclasses.
|
@@ -1276,16 +1275,21 @@ module Sinatra
|
|
1276
1275
|
# Sets an option to the given value. If the value is a proc,
|
1277
1276
|
# the proc will be called every time the option is accessed.
|
1278
1277
|
def set(option, value = (not_set = true), ignore_setter = false, &block)
|
1279
|
-
raise ArgumentError if block
|
1280
|
-
|
1278
|
+
raise ArgumentError if block && !not_set
|
1279
|
+
|
1280
|
+
if block
|
1281
|
+
value = block
|
1282
|
+
not_set = false
|
1283
|
+
end
|
1281
1284
|
|
1282
1285
|
if not_set
|
1283
1286
|
raise ArgumentError unless option.respond_to?(:each)
|
1284
|
-
|
1287
|
+
|
1288
|
+
option.each { |k, v| set(k, v) }
|
1285
1289
|
return self
|
1286
1290
|
end
|
1287
1291
|
|
1288
|
-
if respond_to?("#{option}=")
|
1292
|
+
if respond_to?("#{option}=") && !ignore_setter
|
1289
1293
|
return __send__("#{option}=", value)
|
1290
1294
|
end
|
1291
1295
|
|
@@ -1324,7 +1328,7 @@ module Sinatra
|
|
1324
1328
|
# class, or an HTTP status code to specify which errors should be
|
1325
1329
|
# handled.
|
1326
1330
|
def error(*codes, &block)
|
1327
|
-
args = compile!
|
1331
|
+
args = compile! 'ERROR', /.*/, block
|
1328
1332
|
codes = codes.flat_map(&method(:Array))
|
1329
1333
|
codes << Exception if codes.empty?
|
1330
1334
|
codes << Sinatra::NotFound if codes.include?(404)
|
@@ -1350,7 +1354,7 @@ module Sinatra
|
|
1350
1354
|
# Load embedded templates from the file; uses the caller's __FILE__
|
1351
1355
|
# when no file is specified.
|
1352
1356
|
def inline_templates=(file = nil)
|
1353
|
-
file = (
|
1357
|
+
file = (caller_files.first || File.expand_path($0)) if file.nil? || file == true
|
1354
1358
|
|
1355
1359
|
begin
|
1356
1360
|
io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file)
|
@@ -1359,23 +1363,24 @@ module Sinatra
|
|
1359
1363
|
app, data = nil
|
1360
1364
|
end
|
1361
1365
|
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1366
|
-
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1372
|
-
|
1373
|
-
|
1374
|
-
|
1375
|
-
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1366
|
+
return unless data
|
1367
|
+
|
1368
|
+
encoding = if app && app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m
|
1369
|
+
$2
|
1370
|
+
else
|
1371
|
+
settings.default_encoding
|
1372
|
+
end
|
1373
|
+
|
1374
|
+
lines = app.count("\n") + 1
|
1375
|
+
template = nil
|
1376
|
+
force_encoding data, encoding
|
1377
|
+
data.each_line do |line|
|
1378
|
+
lines += 1
|
1379
|
+
if line =~ /^@@\s*(.*\S)\s*$/
|
1380
|
+
template = force_encoding(String.new, encoding)
|
1381
|
+
templates[$1.to_sym] = [template, file, lines]
|
1382
|
+
elsif template
|
1383
|
+
template << line
|
1379
1384
|
end
|
1380
1385
|
end
|
1381
1386
|
end
|
@@ -1384,8 +1389,10 @@ module Sinatra
|
|
1384
1389
|
def mime_type(type, value = nil)
|
1385
1390
|
return type if type.nil?
|
1386
1391
|
return type.to_s if type.to_s.include?('/')
|
1387
|
-
|
1392
|
+
|
1393
|
+
type = ".#{type}" unless type.to_s[0] == '.'
|
1388
1394
|
return Rack::Mime.mime_type(type, nil) unless value
|
1395
|
+
|
1389
1396
|
Rack::Mime::MIME_TYPES[type] = value
|
1390
1397
|
end
|
1391
1398
|
|
@@ -1394,7 +1401,7 @@ module Sinatra
|
|
1394
1401
|
# mime_types :js # => ['application/javascript', 'text/javascript']
|
1395
1402
|
def mime_types(type)
|
1396
1403
|
type = mime_type type
|
1397
|
-
type =~
|
1404
|
+
type =~ %r{^application/(xml|javascript)$} ? [type, "text/#{$1}"] : [type]
|
1398
1405
|
end
|
1399
1406
|
|
1400
1407
|
# Define a before filter; runs before all requests within the same
|
@@ -1423,7 +1430,7 @@ module Sinatra
|
|
1423
1430
|
end
|
1424
1431
|
|
1425
1432
|
def public=(value)
|
1426
|
-
|
1433
|
+
warn_for_deprecation ':public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead'
|
1427
1434
|
set(:public_folder, value)
|
1428
1435
|
end
|
1429
1436
|
|
@@ -1445,14 +1452,21 @@ module Sinatra
|
|
1445
1452
|
route('HEAD', path, opts, &block)
|
1446
1453
|
end
|
1447
1454
|
|
1448
|
-
def put(path, opts = {}, &
|
1449
|
-
|
1450
|
-
def
|
1451
|
-
|
1452
|
-
def
|
1453
|
-
|
1454
|
-
def
|
1455
|
-
|
1455
|
+
def put(path, opts = {}, &block) route 'PUT', path, opts, &block end
|
1456
|
+
|
1457
|
+
def post(path, opts = {}, &block) route 'POST', path, opts, &block end
|
1458
|
+
|
1459
|
+
def delete(path, opts = {}, &block) route 'DELETE', path, opts, &block end
|
1460
|
+
|
1461
|
+
def head(path, opts = {}, &block) route 'HEAD', path, opts, &block end
|
1462
|
+
|
1463
|
+
def options(path, opts = {}, &block) route 'OPTIONS', path, opts, &block end
|
1464
|
+
|
1465
|
+
def patch(path, opts = {}, &block) route 'PATCH', path, opts, &block end
|
1466
|
+
|
1467
|
+
def link(path, opts = {}, &block) route 'LINK', path, opts, &block end
|
1468
|
+
|
1469
|
+
def unlink(path, opts = {}, &block) route 'UNLINK', path, opts, &block end
|
1456
1470
|
|
1457
1471
|
# Makes the methods defined in the block and in the Modules given
|
1458
1472
|
# in `extensions` available to the handlers and templates
|
@@ -1492,37 +1506,39 @@ module Sinatra
|
|
1492
1506
|
# Stop the self-hosted server if running.
|
1493
1507
|
def quit!
|
1494
1508
|
return unless running?
|
1509
|
+
|
1495
1510
|
# Use Thin's hard #stop! if available, otherwise just #stop.
|
1496
1511
|
running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop
|
1497
|
-
|
1512
|
+
warn '== Sinatra has ended his set (crowd applauds)' unless suppress_messages?
|
1498
1513
|
set :running_server, nil
|
1499
1514
|
set :handler_name, nil
|
1500
1515
|
end
|
1501
1516
|
|
1502
|
-
|
1517
|
+
alias stop! quit!
|
1503
1518
|
|
1504
1519
|
# Run the Sinatra app as a self-hosted server using
|
1505
|
-
# Puma, Mongrel, or WEBrick (in that order). If given a block, will call
|
1520
|
+
# Puma, Falcon, Mongrel, or WEBrick (in that order). If given a block, will call
|
1506
1521
|
# with the constructed handler once we have taken the stage.
|
1507
1522
|
def run!(options = {}, &block)
|
1508
1523
|
return if running?
|
1524
|
+
|
1509
1525
|
set options
|
1510
1526
|
handler = Rack::Handler.pick(server)
|
1511
1527
|
handler_name = handler.name.gsub(/.*::/, '')
|
1512
1528
|
server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
|
1513
|
-
server_settings.merge!(:
|
1529
|
+
server_settings.merge!(Port: port, Host: bind)
|
1514
1530
|
|
1515
1531
|
begin
|
1516
1532
|
start_server(handler, server_settings, handler_name, &block)
|
1517
1533
|
rescue Errno::EADDRINUSE
|
1518
|
-
|
1534
|
+
warn "== Someone is already performing on port #{port}!"
|
1519
1535
|
raise
|
1520
1536
|
ensure
|
1521
1537
|
quit!
|
1522
1538
|
end
|
1523
1539
|
end
|
1524
1540
|
|
1525
|
-
|
1541
|
+
alias start! run!
|
1526
1542
|
|
1527
1543
|
# Check whether the self-hosted server is running or not.
|
1528
1544
|
def running?
|
@@ -1540,8 +1556,8 @@ module Sinatra
|
|
1540
1556
|
# Create a new instance of the class fronted by its middleware
|
1541
1557
|
# pipeline. The object is guaranteed to respond to #call but may not be
|
1542
1558
|
# an instance of the class new was called on.
|
1543
|
-
def new(*args, &
|
1544
|
-
instance = new!(*args, &
|
1559
|
+
def new(*args, &block)
|
1560
|
+
instance = new!(*args, &block)
|
1545
1561
|
Wrapper.new(build(instance).to_app, instance)
|
1546
1562
|
end
|
1547
1563
|
ruby2_keywords :new if respond_to?(:ruby2_keywords, true)
|
@@ -1566,12 +1582,6 @@ module Sinatra
|
|
1566
1582
|
cleaned_caller(1).flatten
|
1567
1583
|
end
|
1568
1584
|
|
1569
|
-
# Like caller_files, but containing Arrays rather than strings with the
|
1570
|
-
# first element being the file, and the second being the line.
|
1571
|
-
def caller_locations
|
1572
|
-
cleaned_caller 2
|
1573
|
-
end
|
1574
|
-
|
1575
1585
|
private
|
1576
1586
|
|
1577
1587
|
# Starts the server by running the Rack Handler.
|
@@ -1582,7 +1592,7 @@ module Sinatra
|
|
1582
1592
|
# Run the instance we created:
|
1583
1593
|
handler.run(self, **server_settings) do |server|
|
1584
1594
|
unless suppress_messages?
|
1585
|
-
|
1595
|
+
warn "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
|
1586
1596
|
end
|
1587
1597
|
|
1588
1598
|
setup_traps
|
@@ -1599,18 +1609,18 @@ module Sinatra
|
|
1599
1609
|
end
|
1600
1610
|
|
1601
1611
|
def setup_traps
|
1602
|
-
|
1603
|
-
at_exit { quit! }
|
1612
|
+
return unless traps?
|
1604
1613
|
|
1605
|
-
|
1606
|
-
old_handler = trap(signal) do
|
1607
|
-
quit!
|
1608
|
-
old_handler.call if old_handler.respond_to?(:call)
|
1609
|
-
end
|
1610
|
-
end
|
1614
|
+
at_exit { quit! }
|
1611
1615
|
|
1612
|
-
|
1616
|
+
%i[INT TERM].each do |signal|
|
1617
|
+
old_handler = trap(signal) do
|
1618
|
+
quit!
|
1619
|
+
old_handler.call if old_handler.respond_to?(:call)
|
1620
|
+
end
|
1613
1621
|
end
|
1622
|
+
|
1623
|
+
set :traps, false
|
1614
1624
|
end
|
1615
1625
|
|
1616
1626
|
# Dynamically defines a method on settings.
|
@@ -1638,18 +1648,21 @@ module Sinatra
|
|
1638
1648
|
end
|
1639
1649
|
end
|
1640
1650
|
end
|
1641
|
-
|
1651
|
+
alias agent user_agent
|
1642
1652
|
|
1643
1653
|
# Condition for matching mimetypes. Accepts file extensions.
|
1644
1654
|
def provides(*types)
|
1645
1655
|
types.map! { |t| mime_types(t) }
|
1646
1656
|
types.flatten!
|
1647
1657
|
condition do
|
1648
|
-
|
1649
|
-
|
1650
|
-
|
1651
|
-
|
1652
|
-
|
1658
|
+
response_content_type = response['Content-Type']
|
1659
|
+
preferred_type = request.preferred_type(types)
|
1660
|
+
|
1661
|
+
if response_content_type
|
1662
|
+
types.include?(response_content_type) || types.include?(response_content_type[/^[^;]+/])
|
1663
|
+
elsif preferred_type
|
1664
|
+
params = (preferred_type.respond_to?(:params) ? preferred_type.params : {})
|
1665
|
+
content_type(preferred_type, params)
|
1653
1666
|
true
|
1654
1667
|
else
|
1655
1668
|
false
|
@@ -1658,7 +1671,7 @@ module Sinatra
|
|
1658
1671
|
end
|
1659
1672
|
|
1660
1673
|
def route(verb, path, options = {}, &block)
|
1661
|
-
enable :empty_path_info if path ==
|
1674
|
+
enable :empty_path_info if path == '' && empty_path_info.nil?
|
1662
1675
|
signature = compile!(verb, path, block, **options)
|
1663
1676
|
(@routes[verb] ||= []) << signature
|
1664
1677
|
invoke_hook(:route_added, verb, path, block)
|
@@ -1687,12 +1700,13 @@ module Sinatra
|
|
1687
1700
|
pattern = compile(path, route_mustermann_opts)
|
1688
1701
|
method_name = "#{verb} #{path}"
|
1689
1702
|
unbound_method = generate_method(method_name, &block)
|
1690
|
-
conditions
|
1691
|
-
|
1692
|
-
|
1693
|
-
proc { |a,
|
1703
|
+
conditions = @conditions
|
1704
|
+
@conditions = []
|
1705
|
+
wrapper = block.arity.zero? ?
|
1706
|
+
proc { |a, _p| unbound_method.bind(a).call } :
|
1707
|
+
proc { |a, p| unbound_method.bind(a).call(*p) }
|
1694
1708
|
|
1695
|
-
[
|
1709
|
+
[pattern, conditions, wrapper]
|
1696
1710
|
end
|
1697
1711
|
|
1698
1712
|
def compile(path, route_mustermann_opts = {})
|
@@ -1710,7 +1724,7 @@ module Sinatra
|
|
1710
1724
|
end
|
1711
1725
|
|
1712
1726
|
def setup_middleware(builder)
|
1713
|
-
middleware.each { |c,a,b| builder.use(c, *a, &b) }
|
1727
|
+
middleware.each { |c, a, b| builder.use(c, *a, &b) }
|
1714
1728
|
end
|
1715
1729
|
|
1716
1730
|
def setup_logging(builder)
|
@@ -1740,9 +1754,10 @@ module Sinatra
|
|
1740
1754
|
|
1741
1755
|
def setup_protection(builder)
|
1742
1756
|
return unless protection?
|
1757
|
+
|
1743
1758
|
options = Hash === protection ? protection.dup : {}
|
1744
1759
|
options = {
|
1745
|
-
img_src:
|
1760
|
+
img_src: "'self' data:",
|
1746
1761
|
font_src: "'self'"
|
1747
1762
|
}.merge options
|
1748
1763
|
|
@@ -1756,6 +1771,7 @@ module Sinatra
|
|
1756
1771
|
|
1757
1772
|
def setup_sessions(builder)
|
1758
1773
|
return unless sessions?
|
1774
|
+
|
1759
1775
|
options = {}
|
1760
1776
|
options[:secret] = session_secret if session_secret?
|
1761
1777
|
options.merge! sessions.to_hash if sessions.respond_to? :to_hash
|
@@ -1778,15 +1794,15 @@ module Sinatra
|
|
1778
1794
|
end
|
1779
1795
|
|
1780
1796
|
# used for deprecation warnings
|
1781
|
-
def
|
1782
|
-
|
1797
|
+
def warn_for_deprecation(message)
|
1798
|
+
warn message + "\n\tfrom #{cleaned_caller.first.join(':')}"
|
1783
1799
|
end
|
1784
1800
|
|
1785
1801
|
# Like Kernel#caller but excluding certain magic entries
|
1786
1802
|
def cleaned_caller(keep = 3)
|
1787
|
-
caller(1)
|
1788
|
-
map!
|
1789
|
-
reject { |file, *_|
|
1803
|
+
caller(1)
|
1804
|
+
.map! { |line| line.split(/:(?=\d|in )/, 3)[0, keep] }
|
1805
|
+
.reject { |file, *_| callers_to_ignore.any? { |pattern| file =~ pattern } }
|
1790
1806
|
end
|
1791
1807
|
end
|
1792
1808
|
|
@@ -1794,6 +1810,7 @@ module Sinatra
|
|
1794
1810
|
# which is UTF-8 by default
|
1795
1811
|
def self.force_encoding(data, encoding = default_encoding)
|
1796
1812
|
return if data == settings || data.is_a?(Tempfile)
|
1813
|
+
|
1797
1814
|
if data.respond_to? :force_encoding
|
1798
1815
|
data.force_encoding(encoding).encode!
|
1799
1816
|
elsif data.respond_to? :each_value
|
@@ -1804,24 +1821,26 @@ module Sinatra
|
|
1804
1821
|
data
|
1805
1822
|
end
|
1806
1823
|
|
1807
|
-
def force_encoding(*args)
|
1824
|
+
def force_encoding(*args)
|
1825
|
+
settings.force_encoding(*args)
|
1826
|
+
end
|
1808
1827
|
|
1809
1828
|
reset!
|
1810
1829
|
|
1811
1830
|
set :environment, (ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym
|
1812
|
-
set :raise_errors,
|
1813
|
-
set :dump_errors,
|
1814
|
-
set :show_exceptions,
|
1831
|
+
set :raise_errors, proc { test? }
|
1832
|
+
set :dump_errors, proc { !test? }
|
1833
|
+
set :show_exceptions, proc { development? }
|
1815
1834
|
set :sessions, false
|
1816
|
-
set :session_store, Rack::
|
1835
|
+
set :session_store, Rack::Protection::EncryptedCookie
|
1817
1836
|
set :logging, false
|
1818
1837
|
set :protection, true
|
1819
1838
|
set :method_override, false
|
1820
1839
|
set :use_code, false
|
1821
|
-
set :default_encoding,
|
1840
|
+
set :default_encoding, 'utf-8'
|
1822
1841
|
set :x_cascade, true
|
1823
1842
|
set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" }
|
1824
|
-
settings.add_charset <<
|
1843
|
+
settings.add_charset << %r{^text/}
|
1825
1844
|
set :mustermann_opts, {}
|
1826
1845
|
set :default_content_type, 'text/html'
|
1827
1846
|
|
@@ -1829,14 +1848,15 @@ module Sinatra
|
|
1829
1848
|
begin
|
1830
1849
|
require 'securerandom'
|
1831
1850
|
set :session_secret, SecureRandom.hex(64)
|
1832
|
-
rescue LoadError, NotImplementedError
|
1851
|
+
rescue LoadError, NotImplementedError, RuntimeError
|
1833
1852
|
# SecureRandom raises a NotImplementedError if no random device is available
|
1834
|
-
|
1853
|
+
# RuntimeError raised due to broken openssl backend: https://bugs.ruby-lang.org/issues/19230
|
1854
|
+
set :session_secret, format('%064x', Kernel.rand((2**256) - 1))
|
1835
1855
|
end
|
1836
1856
|
|
1837
1857
|
class << self
|
1838
|
-
|
1839
|
-
|
1858
|
+
alias methodoverride? method_override?
|
1859
|
+
alias methodoverride= method_override=
|
1840
1860
|
end
|
1841
1861
|
|
1842
1862
|
set :run, false # start server via at-exit hook?
|
@@ -1844,21 +1864,17 @@ module Sinatra
|
|
1844
1864
|
set :handler_name, nil
|
1845
1865
|
set :traps, true
|
1846
1866
|
set :server, %w[HTTP webrick]
|
1847
|
-
set :bind,
|
1867
|
+
set :bind, proc { development? ? 'localhost' : '0.0.0.0' }
|
1848
1868
|
set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567)
|
1849
1869
|
set :quiet, false
|
1850
1870
|
|
1851
1871
|
ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
|
1852
1872
|
|
1853
|
-
|
1854
|
-
|
1855
|
-
|
1856
|
-
|
1857
|
-
|
1858
|
-
server.unshift 'mongrel' if ruby_engine.nil?
|
1859
|
-
server.unshift 'thin' if ruby_engine != 'jruby'
|
1860
|
-
server.unshift 'trinidad' if ruby_engine == 'jruby'
|
1861
|
-
end
|
1873
|
+
server.unshift 'puma'
|
1874
|
+
server.unshift 'falcon' if ruby_engine != 'jruby'
|
1875
|
+
server.unshift 'mongrel' if ruby_engine.nil?
|
1876
|
+
server.unshift 'thin' if ruby_engine != 'jruby'
|
1877
|
+
server.unshift 'trinidad' if ruby_engine == 'jruby'
|
1862
1878
|
|
1863
1879
|
set :absolute_redirects, true
|
1864
1880
|
set :prefixed_redirects, false
|
@@ -1866,14 +1882,14 @@ module Sinatra
|
|
1866
1882
|
set :strict_paths, true
|
1867
1883
|
|
1868
1884
|
set :app_file, nil
|
1869
|
-
set :root,
|
1870
|
-
set :views,
|
1871
|
-
set :reload_templates,
|
1885
|
+
set :root, proc { app_file && File.expand_path(File.dirname(app_file)) }
|
1886
|
+
set :views, proc { root && File.join(root, 'views') }
|
1887
|
+
set :reload_templates, proc { development? }
|
1872
1888
|
set :lock, false
|
1873
1889
|
set :threaded, true
|
1874
1890
|
|
1875
|
-
set :public_folder,
|
1876
|
-
set :static,
|
1891
|
+
set :public_folder, proc { root && File.join(root, 'public') }
|
1892
|
+
set :static, proc { public_folder && File.exist?(public_folder) }
|
1877
1893
|
set :static_cache_control, false
|
1878
1894
|
|
1879
1895
|
error ::Exception do
|
@@ -1892,7 +1908,7 @@ module Sinatra
|
|
1892
1908
|
error NotFound do
|
1893
1909
|
content_type 'text/html'
|
1894
1910
|
|
1895
|
-
if
|
1911
|
+
if instance_of?(Sinatra::Application)
|
1896
1912
|
code = <<-RUBY.gsub(/^ {12}/, '')
|
1897
1913
|
#{request.request_method.downcase} '#{request.path_info}' do
|
1898
1914
|
"Hello World"
|
@@ -1907,11 +1923,11 @@ module Sinatra
|
|
1907
1923
|
end
|
1908
1924
|
RUBY
|
1909
1925
|
|
1910
|
-
file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(
|
1926
|
+
file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(%r{^/}, '')
|
1911
1927
|
code = "# in #{file}\n#{code}" unless file.empty?
|
1912
1928
|
end
|
1913
1929
|
|
1914
|
-
|
1930
|
+
<<-HTML.gsub(/^ {10}/, '')
|
1915
1931
|
<!DOCTYPE html>
|
1916
1932
|
<html>
|
1917
1933
|
<head>
|
@@ -1923,7 +1939,7 @@ module Sinatra
|
|
1923
1939
|
</head>
|
1924
1940
|
<body>
|
1925
1941
|
<h2>Sinatra doesn’t know this ditty.</h2>
|
1926
|
-
<img src='#{uri
|
1942
|
+
<img src='#{uri '/__sinatra__/404.png'}'>
|
1927
1943
|
<div id="c">
|
1928
1944
|
Try this:
|
1929
1945
|
<pre>#{Rack::Utils.escape_html(code)}</pre>
|
@@ -1943,12 +1959,12 @@ module Sinatra
|
|
1943
1959
|
# top-level. Subclassing Sinatra::Base is highly recommended for
|
1944
1960
|
# modular applications.
|
1945
1961
|
class Application < Base
|
1946
|
-
set :logging,
|
1962
|
+
set :logging, proc { !test? }
|
1947
1963
|
set :method_override, true
|
1948
|
-
set :run,
|
1964
|
+
set :run, proc { !test? }
|
1949
1965
|
set :app_file, nil
|
1950
1966
|
|
1951
|
-
def self.register(*extensions, &block)
|
1967
|
+
def self.register(*extensions, &block) # :nodoc:
|
1952
1968
|
added_methods = extensions.flat_map(&:public_instance_methods)
|
1953
1969
|
Delegator.delegate(*added_methods)
|
1954
1970
|
super(*extensions, &block)
|
@@ -1958,11 +1974,12 @@ module Sinatra
|
|
1958
1974
|
# Sinatra delegation mixin. Mixing this module into an object causes all
|
1959
1975
|
# methods to be delegated to the Sinatra::Application class. Used primarily
|
1960
1976
|
# at the top-level.
|
1961
|
-
module Delegator
|
1977
|
+
module Delegator # :nodoc:
|
1962
1978
|
def self.delegate(*methods)
|
1963
1979
|
methods.each do |method_name|
|
1964
1980
|
define_method(method_name) do |*args, &block|
|
1965
1981
|
return super(*args, &block) if respond_to? method_name
|
1982
|
+
|
1966
1983
|
Delegator.target.send(method_name, *args, &block)
|
1967
1984
|
end
|
1968
1985
|
# ensure keyword argument passing is compatible with ruby >= 2.7
|
@@ -1985,7 +2002,8 @@ module Sinatra
|
|
1985
2002
|
|
1986
2003
|
class Wrapper
|
1987
2004
|
def initialize(stack, instance)
|
1988
|
-
@stack
|
2005
|
+
@stack = stack
|
2006
|
+
@instance = instance
|
1989
2007
|
end
|
1990
2008
|
|
1991
2009
|
def settings
|