sinatra 2.0.7 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sinatra might be problematic. Click here for more details.

data/README.pt-pt.md CHANGED
@@ -121,7 +121,7 @@ Arquivos estáticos são disponibilizados a partir do directório
121
121
  `:public_folder`
122
122
 
123
123
  ```ruby
124
- set :public_folder, File.dirname(__FILE__) + '/estatico'
124
+ set :public_folder, __dir__ + '/estatico'
125
125
  ```
126
126
 
127
127
  Note que o nome do directório público não é incluido no URL. Um arquivo
@@ -134,7 +134,7 @@ Templates presumem-se estar localizados sob o directório `./views`. Para
134
134
  utilizar um directório de views diferente:
135
135
 
136
136
  ```ruby
137
- set :views, File.dirname(__FILE__) + '/modelo'
137
+ set :views, __dir__ + '/modelo'
138
138
  ```
139
139
 
140
140
  Uma coisa importante a ser lembrada é que você sempre tem as referências
@@ -757,7 +757,7 @@ Alternativamente, pode adicionar o directório do `sinatra/lib` no
757
757
  `LOAD_PATH` do seu aplicativo:
758
758
 
759
759
  ```ruby
760
- $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib'
760
+ $LOAD_PATH.unshift __dir__ + '/sinatra/lib'
761
761
  require 'rubygems'
762
762
  require 'sinatra'
763
763
 
data/README.ru.md CHANGED
@@ -431,7 +431,7 @@ end
431
431
  месторасположение при помощи опции `:public_folder`:
432
432
 
433
433
  ```ruby
434
- set :public_folder, File.dirname(__FILE__) + '/static'
434
+ set :public_folder, __dir__ + '/static'
435
435
  ```
436
436
 
437
437
  Учтите, что имя директории со статическими файлами не включено в URL.
@@ -3089,9 +3089,9 @@ thin --threaded start
3089
3089
 
3090
3090
  Следующие версии Ruby официально поддерживаются:
3091
3091
  <dl>
3092
- <dt>Ruby 2.2</dt>
3092
+ <dt>Ruby 2.3</dt>
3093
3093
  <dd>
3094
- Версия 2.2 полностью поддерживается и рекомендуется. В настоящее время нет
3094
+ Версия 2.3 полностью поддерживается и рекомендуется. В настоящее время нет
3095
3095
  планов отказаться от официальной поддержки.
3096
3096
  </dd>
3097
3097
 
data/README.zh.md CHANGED
@@ -393,7 +393,7 @@ end
393
393
  静态文件从 `./public` 目录提供服务。可以通过设置`:public_folder` 选项设定一个不同的位置:
394
394
 
395
395
  ```ruby
396
- set :public_folder, File.dirname(__FILE__) + '/static'
396
+ set :public_folder, __dir__ + '/static'
397
397
  ```
398
398
 
399
399
  请注意 public 目录名并没有包含在 URL 中。文件 `./public/css/style.css` 可以通过
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.7
1
+ 2.2.0
data/examples/chat.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby -I ../lib -I lib
2
2
  # coding: utf-8
3
+ require_relative 'rainbows'
3
4
  require 'sinatra'
4
- set :server, 'thin'
5
+ set :server, :rainbows
5
6
  connections = []
6
7
 
7
8
  get '/' do
@@ -0,0 +1,3 @@
1
+ Rainbows! do
2
+ use :EventMachine
3
+ end
@@ -0,0 +1,20 @@
1
+ require 'rainbows'
2
+
3
+ module Rack
4
+ module Handler
5
+ class Rainbows
6
+ def self.run(app, **options)
7
+ rainbows_options = {
8
+ listeners: ["#{options[:Host]}:#{options[:Port]}"],
9
+ worker_processes: 1,
10
+ timeout: 30,
11
+ config_file: ::File.expand_path('rainbows.conf', __dir__),
12
+ }
13
+
14
+ ::Rainbows::HttpServer.new(app, rainbows_options).start.join
15
+ end
16
+ end
17
+
18
+ register :rainbows, ::Rack::Handler::Rainbows
19
+ end
20
+ end
data/examples/stream.ru CHANGED
@@ -2,10 +2,10 @@
2
2
  #
3
3
  # run *one* of these:
4
4
  #
5
- # rackup -s mongrel stream.ru # gem install mongrel
6
- # thin -R stream.ru start # gem install thin
7
- # unicorn stream.ru # gem install unicorn
8
- # puma stream.ru # gem install puma
5
+ # rackup -s mongrel stream.ru # gem install mongrel
6
+ # unicorn stream.ru # gem install unicorn
7
+ # puma stream.ru # gem install puma
8
+ # rainbows -c rainbows.conf stream.ru # gem install rainbows eventmachine
9
9
 
10
10
  require 'sinatra/base'
11
11
 
data/lib/sinatra/base.rb CHANGED
@@ -43,12 +43,11 @@ module Sinatra
43
43
  end
44
44
 
45
45
  def preferred_type(*types)
46
- accepts = accept # just evaluate once
47
- return accepts.first if types.empty?
46
+ return accept.first if types.empty?
48
47
  types.flatten!
49
- return types.first if accepts.empty?
50
- accepts.detect do |pattern|
51
- type = types.detect { |t| File.fnmatch(pattern, t) }
48
+ return types.first if accept.empty?
49
+ accept.detect do |accept_header|
50
+ type = types.detect { |t| MimeTypeEntry.new(t).accepts?(accept_header) }
52
51
  return type if type
53
52
  end
54
53
  end
@@ -79,10 +78,10 @@ module Sinatra
79
78
  super
80
79
  rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
81
80
  raise BadRequest, "Invalid query parameters: #{Rack::Utils.escape_html(e.message)}"
81
+ rescue EOFError => e
82
+ raise BadRequest, "Invalid multipart/form-data: #{Rack::Utils.escape_html(e.message)}"
82
83
  end
83
84
 
84
- private
85
-
86
85
  class AcceptEntry
87
86
  attr_accessor :params
88
87
  attr_reader :entry
@@ -125,6 +124,35 @@ module Sinatra
125
124
  to_str.send(*args, &block)
126
125
  end
127
126
  end
127
+
128
+ class MimeTypeEntry
129
+ attr_reader :params
130
+
131
+ def initialize(entry)
132
+ params = entry.scan(HEADER_PARAM).map! do |s|
133
+ key, value = s.strip.split('=', 2)
134
+ value = value[1..-2].gsub(/\\(.)/, '\1') if value.start_with?('"')
135
+ [key, value]
136
+ end
137
+
138
+ @type = entry[/[^;]+/].delete(' ')
139
+ @params = Hash[params]
140
+ end
141
+
142
+ def accepts?(entry)
143
+ File.fnmatch(entry, self) && matches_params?(entry.params)
144
+ end
145
+
146
+ def to_str
147
+ @type
148
+ end
149
+
150
+ def matches_params?(params)
151
+ return true if @params.empty?
152
+
153
+ params.all? { |k,v| !@params.has_key?(k) || @params[k] == v }
154
+ end
155
+ end
128
156
  end
129
157
 
130
158
  # The response object. See Rack::Response and Rack::Response::Helpers for
@@ -133,10 +161,6 @@ module Sinatra
133
161
  # http://rubydoc.info/github/rack/rack/master/Rack/Response/Helpers
134
162
  class Response < Rack::Response
135
163
  DROP_BODY_RESPONSES = [204, 304]
136
- def initialize(*)
137
- super
138
- headers['Content-Type'] ||= 'text/html'
139
- end
140
164
 
141
165
  def body=(value)
142
166
  value = value.body while Rack::Response === value
@@ -163,10 +187,10 @@ module Sinatra
163
187
  if calculate_content_length?
164
188
  # if some other code has already set Content-Length, don't muck with it
165
189
  # currently, this would be the static file-handler
166
- headers["Content-Length"] = body.inject(0) { |l, p| l + p.bytesize }.to_s
190
+ headers["Content-Length"] = body.map(&:bytesize).reduce(0, :+).to_s
167
191
  end
168
192
 
169
- [status.to_i, headers, result]
193
+ [status, headers, result]
170
194
  end
171
195
 
172
196
  private
@@ -176,15 +200,15 @@ module Sinatra
176
200
  end
177
201
 
178
202
  def drop_content_info?
179
- status.to_i / 100 == 1 or drop_body?
203
+ informational? or drop_body?
180
204
  end
181
205
 
182
206
  def drop_body?
183
- DROP_BODY_RESPONSES.include?(status.to_i)
207
+ DROP_BODY_RESPONSES.include?(status)
184
208
  end
185
209
  end
186
210
 
187
- # Some Rack handlers (Thin, Rainbows!) implement an extended body object protocol, however,
211
+ # Some Rack handlers (Rainbows!) implement an extended body object protocol, however,
188
212
  # some middleware (namely Rack::Lint) will break it by not mirroring the methods in question.
189
213
  # This middleware will detect an extended body object and will make sure it reaches the
190
214
  # handler directly. We do this here, so our middleware and middleware set up by the app will
@@ -451,7 +475,7 @@ module Sinatra
451
475
  #
452
476
  # The close parameter specifies whether Stream#close should be called
453
477
  # after the block has been executed. This is only relevant for evented
454
- # servers like Thin or Rainbows.
478
+ # servers like Rainbows.
455
479
  def stream(keep_open = false)
456
480
  scheduler = env['async.callback'] ? EventMachine : Stream
457
481
  current = @params.dup
@@ -647,8 +671,6 @@ module Sinatra
647
671
  end
648
672
  end
649
673
 
650
- private
651
-
652
674
  # Template rendering methods. Each method takes the name of a template
653
675
  # to render as a Symbol and returns a String with the rendered output,
654
676
  # as well as an optional hash with additional options.
@@ -849,12 +871,12 @@ module Sinatra
849
871
 
850
872
  def compile_template(engine, data, options, views)
851
873
  eat_errors = options.delete :eat_errors
852
- template_cache.fetch engine, data, options, views do
853
- template = Tilt[engine]
854
- raise "Template engine not found: #{engine}" if template.nil?
874
+ template = Tilt[engine]
875
+ raise "Template engine not found: #{engine}" if template.nil?
855
876
 
856
- case data
857
- when Symbol
877
+ case data
878
+ when Symbol
879
+ template_cache.fetch engine, data, options, views do
858
880
  body, path, line = settings.templates[data]
859
881
  if body
860
882
  body = body.call if body.respond_to?(:call)
@@ -872,17 +894,24 @@ module Sinatra
872
894
  throw :layout_missing if eat_errors and not found
873
895
  template.new(path, 1, options)
874
896
  end
875
- when Proc, String
876
- body = data.is_a?(String) ? Proc.new { data } : data
877
- caller = settings.caller_locations.first
878
- path = options[:path] || caller[0]
879
- line = options[:line] || caller[1]
880
- template.new(path, line.to_i, options, &body)
881
- else
882
- raise ArgumentError, "Sorry, don't know how to render #{data.inspect}."
883
897
  end
898
+ when Proc
899
+ compile_block_template(template, options, &data)
900
+ when String
901
+ template_cache.fetch engine, data, options, views do
902
+ compile_block_template(template, options) { data }
903
+ end
904
+ else
905
+ raise ArgumentError, "Sorry, don't know how to render #{data.inspect}."
884
906
  end
885
907
  end
908
+
909
+ def compile_block_template(template, options, &body)
910
+ caller = settings.caller_locations.first
911
+ path = options[:path] || caller[0]
912
+ line = options[:line] || caller[1]
913
+ template.new(path, line.to_i, options, &body)
914
+ end
886
915
  end
887
916
 
888
917
  # Base class for all Sinatra applications and middleware.
@@ -896,10 +925,11 @@ module Sinatra
896
925
  attr_accessor :app, :env, :request, :response, :params
897
926
  attr_reader :template_cache
898
927
 
899
- def initialize(app = nil)
928
+ def initialize(app = nil, **kwargs)
900
929
  super()
901
930
  @app = app
902
931
  @template_cache = Tilt::Cache.new
932
+ @pinned_response = nil # whether a before! filter pinned the content-type
903
933
  yield self if block_given?
904
934
  end
905
935
 
@@ -913,17 +943,17 @@ module Sinatra
913
943
  @params = IndifferentHash.new
914
944
  @request = Request.new(env)
915
945
  @response = Response.new
946
+ @pinned_response = nil
916
947
  template_cache.clear if settings.reload_templates
917
948
 
918
- @response['Content-Type'] = nil
919
949
  invoke { dispatch! }
920
950
  invoke { error_block!(response.status) } unless @env['sinatra.error']
921
951
 
922
952
  unless @response['Content-Type']
923
- if Array === body and body[0].respond_to? :content_type
953
+ if Array === body && body[0].respond_to?(:content_type)
924
954
  content_type body[0].content_type
925
- else
926
- content_type :html
955
+ elsif default = settings.default_content_type
956
+ content_type default
927
957
  end
928
958
  end
929
959
 
@@ -973,15 +1003,21 @@ module Sinatra
973
1003
  private
974
1004
 
975
1005
  # Run filters defined on the class and all superclasses.
976
- def filter!(type, base = settings)
977
- filter! type, base.superclass if base.superclass.respond_to?(:filters)
978
- base.filters[type].each { |args| process_route(*args) }
1006
+ # Accepts an optional block to call after each filter is applied.
1007
+ def filter!(type, base = settings, &block)
1008
+ filter!(type, base.superclass, &block) if base.superclass.respond_to?(:filters)
1009
+ base.filters[type].each do |args|
1010
+ result = process_route(*args)
1011
+ block.call(result) if block_given?
1012
+ end
979
1013
  end
980
1014
 
981
1015
  # Run routes defined on the class and all superclasses.
982
1016
  def route!(base = settings, pass_block = nil)
983
1017
  if routes = base.routes[@request.request_method]
984
1018
  routes.each do |pattern, conditions, block|
1019
+ response.delete_header('Content-Type') unless @pinned_response
1020
+
985
1021
  returned_pass_block = process_route(pattern, conditions) do |*args|
986
1022
  env['sinatra.route'] = "#{@request.request_method} #{pattern}"
987
1023
  route_eval { block[*args] }
@@ -1019,7 +1055,7 @@ module Sinatra
1019
1055
 
1020
1056
  params.delete("ignore") # TODO: better params handling, maybe turn it into "smart" object or detect changes
1021
1057
  force_encoding(params)
1022
- original, @params = @params, @params.merge(params) if params.any?
1058
+ @params = @params.merge(params) if params.any?
1023
1059
 
1024
1060
  regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? {|subpattern| subpattern.is_a?(Mustermann::Regular)} )
1025
1061
  if regexp_exists
@@ -1038,7 +1074,8 @@ module Sinatra
1038
1074
  @env['sinatra.error.params'] = @params
1039
1075
  raise
1040
1076
  ensure
1041
- @params = original if original
1077
+ params ||= {}
1078
+ params.each { |k, _| @params.delete(k) } unless @env['sinatra.error.params']
1042
1079
  end
1043
1080
 
1044
1081
  # No matching route was found or all routes passed. The default
@@ -1050,7 +1087,7 @@ module Sinatra
1050
1087
  if @app
1051
1088
  forward
1052
1089
  else
1053
- raise NotFound
1090
+ raise NotFound, "#{request.request_method} #{request.path_info}"
1054
1091
  end
1055
1092
  end
1056
1093
 
@@ -1058,7 +1095,11 @@ module Sinatra
1058
1095
  # a matching file is found, returns nil otherwise.
1059
1096
  def static!(options = {})
1060
1097
  return if (public_dir = settings.public_folder).nil?
1061
- path = File.expand_path("#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}" )
1098
+ path = "#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}"
1099
+ return unless valid_path?(path)
1100
+
1101
+ path = File.expand_path(path)
1102
+ return unless path.start_with?(File.expand_path(public_dir) + '/')
1062
1103
  return unless File.file?(path)
1063
1104
 
1064
1105
  env['sinatra.static_file'] = path
@@ -1093,7 +1134,9 @@ module Sinatra
1093
1134
 
1094
1135
  invoke do
1095
1136
  static! if settings.static? && (request.get? || request.head?)
1096
- filter! :before
1137
+ filter! :before do
1138
+ @pinned_response = !response['Content-Type'].nil?
1139
+ end
1097
1140
  route!
1098
1141
  end
1099
1142
  rescue ::Exception => boom
@@ -1113,7 +1156,7 @@ module Sinatra
1113
1156
  end
1114
1157
  @env['sinatra.error'] = boom
1115
1158
 
1116
- if boom.respond_to? :http_status
1159
+ if boom.respond_to? :http_status and boom.http_status.between? 400, 599
1117
1160
  status(boom.http_status)
1118
1161
  elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599
1119
1162
  status(boom.code)
@@ -1121,21 +1164,27 @@ module Sinatra
1121
1164
  status(500)
1122
1165
  end
1123
1166
 
1124
- status(500) unless status.between? 400, 599
1125
-
1126
- boom_message = boom.message if boom.message && boom.message != boom.class.name
1127
1167
  if server_error?
1128
1168
  dump_errors! boom if settings.dump_errors?
1129
1169
  raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
1130
1170
  elsif not_found?
1131
1171
  headers['X-Cascade'] = 'pass' if settings.x_cascade?
1132
- body boom_message || '<h1>Not Found</h1>'
1133
- elsif bad_request?
1134
- body boom_message || '<h1>Bad Request</h1>'
1135
1172
  end
1136
1173
 
1137
- res = error_block!(boom.class, boom) || error_block!(status, boom)
1138
- return res if res or not server_error?
1174
+ if res = error_block!(boom.class, boom) || error_block!(status, boom)
1175
+ return res
1176
+ end
1177
+
1178
+ if not_found? || bad_request?
1179
+ if boom.message && boom.message != boom.class.name
1180
+ body Rack::Utils.escape_html(boom.message)
1181
+ else
1182
+ content_type 'text/html'
1183
+ body '<h1>' + (not_found? ? 'Not Found' : 'Bad Request') + '</h1>'
1184
+ end
1185
+ end
1186
+
1187
+ return unless server_error?
1139
1188
  raise boom if settings.raise_errors? or settings.show_exceptions?
1140
1189
  error_block! Exception, boom
1141
1190
  end
@@ -1345,19 +1394,19 @@ module Sinatra
1345
1394
  # context as route handlers and may access/modify the request and
1346
1395
  # response.
1347
1396
  def before(path = /.*/, **options, &block)
1348
- add_filter(:before, path, options, &block)
1397
+ add_filter(:before, path, **options, &block)
1349
1398
  end
1350
1399
 
1351
1400
  # Define an after filter; runs after all requests within the same
1352
1401
  # context as route handlers and may access/modify the request and
1353
1402
  # response.
1354
1403
  def after(path = /.*/, **options, &block)
1355
- add_filter(:after, path, options, &block)
1404
+ add_filter(:after, path, **options, &block)
1356
1405
  end
1357
1406
 
1358
1407
  # add a filter
1359
1408
  def add_filter(type, path = /.*/, **options, &block)
1360
- filters[type] << compile!(type, path, block, options)
1409
+ filters[type] << compile!(type, path, block, **options)
1361
1410
  end
1362
1411
 
1363
1412
  # Add a route condition. The route is considered non-matching when the
@@ -1431,6 +1480,7 @@ module Sinatra
1431
1480
  @prototype = nil
1432
1481
  @middleware << [middleware, args, block]
1433
1482
  end
1483
+ ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true)
1434
1484
 
1435
1485
  # Stop the self-hosted server if running.
1436
1486
  def quit!
@@ -1445,12 +1495,12 @@ module Sinatra
1445
1495
  alias_method :stop!, :quit!
1446
1496
 
1447
1497
  # Run the Sinatra app as a self-hosted server using
1448
- # Thin, Puma, Mongrel, or WEBrick (in that order). If given a block, will call
1498
+ # Puma, Mongrel, or WEBrick (in that order). If given a block, will call
1449
1499
  # with the constructed handler once we have taken the stage.
1450
1500
  def run!(options = {}, &block)
1451
1501
  return if running?
1452
1502
  set options
1453
- handler = detect_rack_handler
1503
+ handler = Rack::Handler.pick(server)
1454
1504
  handler_name = handler.name.gsub(/.*::/, '')
1455
1505
  server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
1456
1506
  server_settings.merge!(:Port => port, :Host => bind)
@@ -1483,8 +1533,8 @@ module Sinatra
1483
1533
  # Create a new instance of the class fronted by its middleware
1484
1534
  # pipeline. The object is guaranteed to respond to #call but may not be
1485
1535
  # an instance of the class new was called on.
1486
- def new(*args, &bk)
1487
- instance = new!(*args, &bk)
1536
+ def new(*args, **kwargs, &bk)
1537
+ instance = new!(*args, **kwargs, &bk)
1488
1538
  Wrapper.new(build(instance).to_app, instance)
1489
1539
  end
1490
1540
 
@@ -1522,7 +1572,7 @@ module Sinatra
1522
1572
  # behavior, by ensuring an instance exists:
1523
1573
  prototype
1524
1574
  # Run the instance we created:
1525
- handler.run(self, server_settings) do |server|
1575
+ handler.run(self, **server_settings) do |server|
1526
1576
  unless suppress_messages?
1527
1577
  $stderr.puts "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
1528
1578
  end
@@ -1601,7 +1651,7 @@ module Sinatra
1601
1651
 
1602
1652
  def route(verb, path, options = {}, &block)
1603
1653
  enable :empty_path_info if path == "" and empty_path_info.nil?
1604
- signature = compile!(verb, path, block, options)
1654
+ signature = compile!(verb, path, block, **options)
1605
1655
  (@routes[verb] ||= []) << signature
1606
1656
  invoke_hook(:route_added, verb, path, block)
1607
1657
  signature
@@ -1638,7 +1688,7 @@ module Sinatra
1638
1688
  end
1639
1689
 
1640
1690
  def compile(path, route_mustermann_opts = {})
1641
- Mustermann.new(path, mustermann_opts.merge(route_mustermann_opts))
1691
+ Mustermann.new(path, **mustermann_opts.merge(route_mustermann_opts))
1642
1692
  end
1643
1693
 
1644
1694
  def setup_default_middleware(builder)
@@ -1704,17 +1754,6 @@ module Sinatra
1704
1754
  builder.use session_store, options
1705
1755
  end
1706
1756
 
1707
- def detect_rack_handler
1708
- servers = Array(server)
1709
- servers.each do |server_name|
1710
- begin
1711
- return Rack::Handler.get(server_name.to_s)
1712
- rescue LoadError, NameError
1713
- end
1714
- end
1715
- fail "Server handler (#{servers.join(',')}) not found."
1716
- end
1717
-
1718
1757
  def inherited(subclass)
1719
1758
  subclass.reset!
1720
1759
  subclass.set :app_file, caller_files.first unless subclass.app_file?
@@ -1776,6 +1815,7 @@ module Sinatra
1776
1815
  set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" }
1777
1816
  settings.add_charset << /^text\//
1778
1817
  set :mustermann_opts, {}
1818
+ set :default_content_type, 'text/html'
1779
1819
 
1780
1820
  # explicitly generating a session secret eagerly to play nice with preforking
1781
1821
  begin
@@ -1836,7 +1876,7 @@ module Sinatra
1836
1876
 
1837
1877
  configure :development do
1838
1878
  get '/__sinatra__/:image.png' do
1839
- filename = File.dirname(__FILE__) + "/images/#{params[:image].to_i}.png"
1879
+ filename = __dir__ + "/images/#{params[:image].to_i}.png"
1840
1880
  content_type :png
1841
1881
  send_file filename
1842
1882
  end
@@ -1917,6 +1957,8 @@ module Sinatra
1917
1957
  return super(*args, &block) if respond_to? method_name
1918
1958
  Delegator.target.send(method_name, *args, &block)
1919
1959
  end
1960
+ # ensure keyword argument passing is compatible with ruby >= 2.7
1961
+ ruby2_keywords(method_name) if respond_to?(:ruby2_keywords, true)
1920
1962
  private method_name
1921
1963
  end
1922
1964
  end
@@ -132,13 +132,17 @@ module Sinatra
132
132
  super(*keys)
133
133
  end
134
134
 
135
- def merge!(other_hash)
136
- return super if other_hash.is_a?(self.class)
137
-
138
- other_hash.each_pair do |key, value|
139
- key = convert_key(key)
140
- value = yield(key, self[key], value) if block_given? && key?(key)
141
- self[key] = convert_value(value)
135
+ def merge!(*other_hashes)
136
+ other_hashes.each do |other_hash|
137
+ if other_hash.is_a?(self.class)
138
+ super(other_hash)
139
+ else
140
+ other_hash.each_pair do |key, value|
141
+ key = convert_key(key)
142
+ value = yield(key, self[key], value) if block_given? && key?(key)
143
+ self[key] = convert_value(value)
144
+ end
145
+ end
142
146
  end
143
147
 
144
148
  self
@@ -146,8 +150,8 @@ module Sinatra
146
150
 
147
151
  alias_method :update, :merge!
148
152
 
149
- def merge(other_hash, &block)
150
- dup.merge!(other_hash, &block)
153
+ def merge(*other_hashes, &block)
154
+ dup.merge!(*other_hashes, &block)
151
155
  end
152
156
 
153
157
  def replace(other_hash)
@@ -176,6 +180,20 @@ module Sinatra
176
180
  end
177
181
  end
178
182
 
183
+ def select(*args, &block)
184
+ return to_enum(:select) unless block_given?
185
+ dup.tap { |hash| hash.select!(*args, &block) }
186
+ end
187
+
188
+ def reject(*args, &block)
189
+ return to_enum(:reject) unless block_given?
190
+ dup.tap { |hash| hash.reject!(*args, &block) }
191
+ end
192
+
193
+ def compact
194
+ dup.tap(&:compact!)
195
+ end if method_defined?(:compact) # Added in Ruby 2.4
196
+
179
197
  private
180
198
 
181
199
  def convert_key(key)
data/lib/sinatra/main.rb CHANGED
@@ -4,11 +4,11 @@ module Sinatra
4
4
  if ARGV.any?
5
5
  require 'optparse'
6
6
  parser = OptionParser.new { |op|
7
- op.on('-p port', 'set the port (default is 4567)') { |val| ParamsConfig[:port] = Integer(val) }
8
- op.on('-s server', 'specify rack server/handler (default is thin)') { |val| ParamsConfig[:server] = val }
9
- op.on('-q', 'turn on quiet mode (default is off)') { ParamsConfig[:quiet] = true }
10
- op.on('-x', 'turn on the mutex lock (default is off)') { ParamsConfig[:lock] = true }
11
- op.on('-e env', 'set the environment (default is development)') do |val|
7
+ op.on('-p port', 'set the port (default is 4567)') { |val| ParamsConfig[:port] = Integer(val) }
8
+ op.on('-s server', 'specify rack server/handler') { |val| ParamsConfig[:server] = val }
9
+ op.on('-q', 'turn on quiet mode (default is off)') { ParamsConfig[:quiet] = true }
10
+ op.on('-x', 'turn on the mutex lock (default is off)') { ParamsConfig[:lock] = true }
11
+ op.on('-e env', 'set the environment (default is development)') do |val|
12
12
  ENV['RACK_ENV'] = val
13
13
  ParamsConfig[:environment] = val.to_sym
14
14
  end