roda 3.108.0 → 3.109.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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/lib/roda/opts.rb +22 -0
  3. data/lib/roda/plugins/Integer_matcher_max.rb +1 -1
  4. data/lib/roda/plugins/_add_script_name.rb +14 -0
  5. data/lib/roda/plugins/_json.rb +22 -0
  6. data/lib/roda/plugins/_symbol_class_matchers.rb +6 -6
  7. data/lib/roda/plugins/additional_render_engines.rb +5 -1
  8. data/lib/roda/plugins/additional_view_directories.rb +5 -1
  9. data/lib/roda/plugins/assets.rb +7 -10
  10. data/lib/roda/plugins/autoload_hash_branches.rb +9 -3
  11. data/lib/roda/plugins/autoload_named_routes.rb +9 -3
  12. data/lib/roda/plugins/capture_erb.rb +5 -1
  13. data/lib/roda/plugins/chunked.rb +11 -5
  14. data/lib/roda/plugins/class_level_routing.rb +5 -3
  15. data/lib/roda/plugins/class_matchers.rb +3 -1
  16. data/lib/roda/plugins/common_logger.rb +7 -3
  17. data/lib/roda/plugins/conditional_sessions.rb +1 -1
  18. data/lib/roda/plugins/content_for.rb +5 -1
  19. data/lib/roda/plugins/content_security_policy.rb +6 -2
  20. data/lib/roda/plugins/cookie_flags.rb +6 -2
  21. data/lib/roda/plugins/cookies.rb +7 -3
  22. data/lib/roda/plugins/custom_block_results.rb +5 -3
  23. data/lib/roda/plugins/custom_matchers.rb +4 -2
  24. data/lib/roda/plugins/default_headers.rb +2 -5
  25. data/lib/roda/plugins/default_status.rb +1 -0
  26. data/lib/roda/plugins/each_part.rb +19 -23
  27. data/lib/roda/plugins/environments.rb +3 -7
  28. data/lib/roda/plugins/error_email.rb +9 -5
  29. data/lib/roda/plugins/error_handler.rb +5 -3
  30. data/lib/roda/plugins/error_mail.rb +6 -2
  31. data/lib/roda/plugins/hash_branch_view_subdir.rb +6 -4
  32. data/lib/roda/plugins/hash_branches.rb +7 -5
  33. data/lib/roda/plugins/hash_paths.rb +7 -5
  34. data/lib/roda/plugins/hash_public.rb +11 -7
  35. data/lib/roda/plugins/hash_public_cache.rb +9 -7
  36. data/lib/roda/plugins/hash_routes.rb +5 -3
  37. data/lib/roda/plugins/heartbeat.rb +6 -2
  38. data/lib/roda/plugins/hmac_paths.rb +9 -3
  39. data/lib/roda/plugins/hooks.rb +13 -10
  40. data/lib/roda/plugins/host_authorization.rb +7 -2
  41. data/lib/roda/plugins/host_routing.rb +7 -2
  42. data/lib/roda/plugins/invalid_request_body.rb +20 -4
  43. data/lib/roda/plugins/ip_from_header.rb +5 -1
  44. data/lib/roda/plugins/json.rb +10 -8
  45. data/lib/roda/plugins/json_parser.rb +26 -12
  46. data/lib/roda/plugins/mail_processor.rb +9 -6
  47. data/lib/roda/plugins/mailer.rb +5 -3
  48. data/lib/roda/plugins/match_affix.rb +6 -1
  49. data/lib/roda/plugins/match_hook_args.rb +7 -5
  50. data/lib/roda/plugins/middleware.rb +13 -7
  51. data/lib/roda/plugins/module_include.rb +1 -1
  52. data/lib/roda/plugins/multi_public.rb +6 -2
  53. data/lib/roda/plugins/multi_route.rb +1 -1
  54. data/lib/roda/plugins/multi_run.rb +13 -17
  55. data/lib/roda/plugins/named_routes.rb +8 -6
  56. data/lib/roda/plugins/named_templates.rb +5 -3
  57. data/lib/roda/plugins/path.rb +16 -14
  58. data/lib/roda/plugins/path_rewriter.rb +8 -5
  59. data/lib/roda/plugins/permissions_policy.rb +7 -3
  60. data/lib/roda/plugins/public.rb +11 -10
  61. data/lib/roda/plugins/recheck_precompiled_assets.rb +1 -1
  62. data/lib/roda/plugins/redirect_http_to_https.rb +6 -3
  63. data/lib/roda/plugins/render.rb +34 -49
  64. data/lib/roda/plugins/render_coverage.rb +6 -3
  65. data/lib/roda/plugins/render_locals.rb +12 -6
  66. data/lib/roda/plugins/request_aref.rb +1 -1
  67. data/lib/roda/plugins/response_content_type.rb +7 -3
  68. data/lib/roda/plugins/route_csrf.rb +7 -2
  69. data/lib/roda/plugins/run_append_slash.rb +5 -1
  70. data/lib/roda/plugins/sec_fetch_site_csrf.rb +6 -2
  71. data/lib/roda/plugins/sessions.rb +15 -11
  72. data/lib/roda/plugins/shape_friendly.rb +3 -1
  73. data/lib/roda/plugins/sinatra_helpers.rb +7 -3
  74. data/lib/roda/plugins/status_handler.rb +5 -3
  75. data/lib/roda/plugins/strip_path_prefix.rb +4 -2
  76. data/lib/roda/plugins/symbol_matchers.rb +3 -1
  77. data/lib/roda/plugins/symbol_views.rb +1 -1
  78. data/lib/roda/plugins/timestamp_public.rb +8 -4
  79. data/lib/roda/plugins/type_routing.rb +9 -5
  80. data/lib/roda/plugins/typecast_params.rb +2 -2
  81. data/lib/roda/plugins/typecast_params_sized_integers.rb +1 -1
  82. data/lib/roda/plugins/url_escape.rb +2 -14
  83. data/lib/roda/plugins.rb +10 -0
  84. data/lib/roda/version.rb +1 -1
  85. data/lib/roda.rb +25 -14
  86. metadata +6 -3
@@ -256,6 +256,11 @@ class Roda
256
256
  end
257
257
  end
258
258
 
259
+ module ClassMethods
260
+ RodaPlugins.opt_attr_reader(self, :absolute_redirects, name: :absolute_redirects?)
261
+ RodaPlugins.opt_attr_reader(self, :prefixed_redirects, name: :prefixed_redirects?)
262
+ end
263
+
259
264
  module RequestMethods
260
265
  # Alias for referrer
261
266
  def back
@@ -285,9 +290,8 @@ class Roda
285
290
  # If the absolute_redirects or :prefixed_redirects roda class options has been set, respect those
286
291
  # and update the path.
287
292
  def redirect(path=(no_add_script_name = true; default_redirect_path), status=default_redirect_status)
288
- opts = roda_class.opts
289
- absolute_redirects = opts[:absolute_redirects]
290
- prefixed_redirects = no_add_script_name ? false : opts[:prefixed_redirects]
293
+ absolute_redirects = roda_class.absolute_redirects?
294
+ prefixed_redirects = no_add_script_name ? false : roda_class.prefixed_redirects?
291
295
  path = uri(path, absolute_redirects, prefixed_redirects) if absolute_redirects || prefixed_redirects
292
296
  super(path, status)
293
297
  end
@@ -37,6 +37,8 @@ class Roda
37
37
  end
38
38
 
39
39
  module ClassMethods
40
+ RodaPlugins.opt_attr_reader(self, :status_handler, name: :status_handlers)
41
+
40
42
  # Install the given block as a status handler for the given HTTP response code.
41
43
  def status_handler(code, opts=OPTS, &block)
42
44
  # For backwards compatibility, pass request argument if block accepts argument
@@ -54,7 +56,7 @@ class Roda
54
56
  end
55
57
 
56
58
  meth = define_roda_method(:"_roda_status_handler__#{code}", arity, &block)
57
- self.opts[:status_handler][code] = define_roda_method(:"_roda_status_handler_#{code}", 1) do |result|
59
+ status_handlers[code] = define_roda_method(:"_roda_status_handler_#{code}", 1) do |result|
58
60
  res = @_response
59
61
  res.status = result[0]
60
62
  handle_headers.call(res.headers)
@@ -64,7 +66,7 @@ class Roda
64
66
 
65
67
  # Freeze the hash of status handlers so that there can be no thread safety issues at runtime.
66
68
  def freeze
67
- opts[:status_handler].freeze
69
+ status_handlers.freeze
68
70
  super
69
71
  end
70
72
  end
@@ -74,7 +76,7 @@ class Roda
74
76
 
75
77
  # If routing returns a response we have a handler for, call that handler.
76
78
  def _roda_after_20__status_handler(result)
77
- if result && (meth = opts[:status_handler][result[0]]) && (v = result[2]).is_a?(Array) && v.empty?
79
+ if result && (meth = self.class.status_handlers[result[0]]) && (v = result[2]).is_a?(Array) && v.empty?
78
80
  send(meth, result)
79
81
  end
80
82
  end
@@ -21,9 +21,11 @@ class Roda
21
21
  end
22
22
 
23
23
  module ClassMethods
24
+ RodaPlugins.opt_attr_reader(self, :strip_path_prefix, name: :strip_path_prefix_regexp)
25
+
24
26
  # Strip the path prefix from the gien path if it starts with the prefix.
25
- def expand_path(path, root=opts[:root])
26
- super.sub(opts[:strip_path_prefix], '')
27
+ def expand_path(path, root=app_root)
28
+ super.sub(strip_path_prefix_regexp, '')
27
29
  end
28
30
  end
29
31
  end
@@ -121,6 +121,8 @@ class Roda
121
121
  end
122
122
 
123
123
  module ClassMethods
124
+ RodaPlugins.opt_attr_reader(self, :symbol_matchers)
125
+
124
126
  # Set the matcher and block to use for the given class.
125
127
  # The matcher can be a regexp, registered symbol matcher, or registered class
126
128
  # matcher (if using the class_matchers plugin).
@@ -141,7 +143,7 @@ class Roda
141
143
 
142
144
  # Freeze the class_matchers hash when freezing the app.
143
145
  def freeze
144
- opts[:symbol_matchers].freeze
146
+ symbol_matchers.freeze
145
147
  super
146
148
  end
147
149
  end
@@ -28,7 +28,7 @@ class Roda
28
28
  end
29
29
 
30
30
  def self.configure(app)
31
- app.opts[:custom_block_results][Symbol] = :view
31
+ app.custom_block_results[Symbol] = :view
32
32
  end
33
33
  end
34
34
 
@@ -46,7 +46,11 @@ class Roda
46
46
  #
47
47
  # The options given are also passed to the public plugin.
48
48
  def self.configure(app, opts=OPTS)
49
- app.opts[:timestamp_public_prefix] = (opts[:prefix] || app.opts[:timestamp_public_prefix] || "static").dup.freeze
49
+ app.opts[:timestamp_public_prefix] = (opts[:prefix] || app.timestamp_public_prefix || "static").dup.freeze
50
+ end
51
+
52
+ module ClassMethods
53
+ RodaPlugins.opt_attr_reader(self, :timestamp_public_prefix)
50
54
  end
51
55
 
52
56
  module InstanceMethods
@@ -54,8 +58,8 @@ class Roda
54
58
  # This does not check the file is inside the directory for performance reasons,
55
59
  # so this should not be called with untrusted input.
56
60
  def timestamp_path(file)
57
- mtime = File.mtime(File.join(opts[:public_root], file))
58
- "/#{opts[:timestamp_public_prefix]}/#{sprintf("%i%06i", mtime.to_i, mtime.usec)}/#{file}"
61
+ mtime = File.mtime(File.join(self.class.public_root, file))
62
+ "/#{self.class.timestamp_public_prefix}/#{sprintf("%i%06i", mtime.to_i, mtime.usec)}/#{file}"
59
63
  end
60
64
  end
61
65
 
@@ -65,7 +69,7 @@ class Roda
65
69
  # a integer segment for the timestamp, and this is a GET request.
66
70
  def timestamp_public
67
71
  if is_get?
68
- on roda_class.opts[:timestamp_public_prefix], Integer do |_|
72
+ on roda_class.timestamp_public_prefix, Integer do |_|
69
73
  public
70
74
  end
71
75
  end
@@ -110,7 +110,7 @@ class Roda
110
110
  }.freeze
111
111
 
112
112
  def self.configure(app, opts = {})
113
- config = (app.opts[:type_routing] || CONFIGURATION).dup
113
+ config = (app.type_routing_opts || CONFIGURATION).dup
114
114
  [:use_extension, :use_header, :default_type].each do |key|
115
115
  config[key] = opts[key] if opts.has_key?(key)
116
116
  end
@@ -149,12 +149,16 @@ class Roda
149
149
  app.opts[:type_routing] = config.freeze
150
150
  end
151
151
 
152
+ module ClassMethods
153
+ RodaPlugins.opt_attr_reader(self, :type_routing, name: :type_routing_opts)
154
+ end
155
+
152
156
  module RequestMethods
153
157
  # Yields if the given +type+ matches the requested data type and halts
154
158
  # the request afterwards, returning the result of the block.
155
159
  def on_type(type, &block)
156
160
  return unless type == requested_type
157
- response[RodaResponseHeaders::CONTENT_TYPE] ||= @scope.opts[:type_routing][:types][type]
161
+ response[RodaResponseHeaders::CONTENT_TYPE] ||= roda_class.type_routing_opts[:types][type]
158
162
  always(&block)
159
163
  end
160
164
 
@@ -162,7 +166,7 @@ class Roda
162
166
  def requested_type
163
167
  return @requested_type if @requested_type
164
168
 
165
- opts = @scope.opts[:type_routing]
169
+ opts = roda_class.type_routing_opts
166
170
  @requested_type = accept_response_type if opts[:use_header]
167
171
  @requested_type ||= opts[:default_type]
168
172
  end
@@ -182,7 +186,7 @@ class Roda
182
186
  # Removes a trailing file extension from the path, and sets
183
187
  # the requested type if so.
184
188
  def _remaining_path(env)
185
- opts = scope.opts[:type_routing]
189
+ opts = roda_class.type_routing_opts
186
190
  path = super
187
191
 
188
192
  if opts[:use_extension]
@@ -197,7 +201,7 @@ class Roda
197
201
 
198
202
  # The response type indicated by the Accept request header.
199
203
  def accept_response_type
200
- mimes = @scope.opts[:type_routing][:mimes]
204
+ mimes = roda_class.type_routing_opts[:mimes]
201
205
  response[RodaResponseHeaders::VARY] = (vary = response[RodaResponseHeaders::VARY]) ? "#{vary}, Accept" : 'Accept'
202
206
 
203
207
  @env['HTTP_ACCEPT'].to_s.split(/\s*,\s*/).map do |part|
@@ -1122,7 +1122,7 @@ class Roda
1122
1122
  # before processing them.
1123
1123
  def self.configure(app, opts=OPTS, &block)
1124
1124
  app.const_set(:TypecastParams, Class.new(RodaPlugins::TypecastParams::Params)) unless app.const_defined?(:TypecastParams)
1125
- app::TypecastParams.class_eval(&block) if block
1125
+ app::TypecastParams.class_eval(&block) if block # RODA4: use class_exec
1126
1126
  if opts[:strip] == :all
1127
1127
  app::TypecastParams.send(:include, StringStripper)
1128
1128
  end
@@ -1133,7 +1133,7 @@ class Roda
1133
1133
  app::TypecastParams.send(:include, SkipBytesizeChecking)
1134
1134
  end
1135
1135
  if opts[:date_parse_input_handler]
1136
- app::TypecastParams.class_eval do
1136
+ app::TypecastParams.class_exec do
1137
1137
  include DateParseInputHandler
1138
1138
  define_method(:handle_date_parse_input, &opts[:date_parse_input_handler])
1139
1139
  private :handle_date_parse_input
@@ -90,7 +90,7 @@ class Roda
90
90
  end
91
91
 
92
92
  if default = opts[:default_size]
93
- app::TypecastParams.class_eval do
93
+ app::TypecastParams.class_exec do
94
94
  meths = ['', 'convert_', '_convert_array_', '_max_input_bytesize_for_', '_invalid_value_message_for_']
95
95
  %w[int uint pos_int pos_uint Integer Integeru].each do |type|
96
96
  meths.each do |prefix|
@@ -16,20 +16,8 @@ class Roda
16
16
  # end
17
17
  module UrlEscape
18
18
  module InstanceMethods
19
- if RUBY_VERSION >= '2'
20
- define_method(:url_escape, Rack::Utils.instance_method(:escape))
21
- define_method(:url_unescape, Rack::Utils.instance_method(:unescape))
22
- # simplecov:disable
23
- else
24
- def url_escape(v)
25
- Rack::Utils.escape(v)
26
- end
27
-
28
- def url_unescape(v)
29
- Rack::Utils.unescape(v)
30
- end
31
- end
32
- # simplecov:enable
19
+ define_method(:url_escape, Rack::Utils.instance_method(:escape))
20
+ define_method(:url_unescape, Rack::Utils.instance_method(:unescape))
33
21
  end
34
22
  end
35
23
 
data/lib/roda/plugins.rb CHANGED
@@ -32,6 +32,16 @@ class Roda
32
32
  plugin
33
33
  end
34
34
 
35
+ # Define an attr_reader method for name that uses the instance variable
36
+ # <tt>@opt_*</tt>. Designed to be used with instance variables set via RodaOpts#[]=.
37
+ def self.opt_attr_reader(mod, key, name: key)
38
+ opt_name = :"opt_#{key}"
39
+ mod.send(:attr_reader, opt_name)
40
+ mod.send(:alias_method, name, opt_name)
41
+ mod.send(:remove_method, opt_name)
42
+ nil
43
+ end
44
+
35
45
  # Register the given plugin with Roda, so that it can be loaded using #plugin
36
46
  # with a symbol. Should be used by plugin files. Example:
37
47
  #
data/lib/roda/version.rb CHANGED
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 3
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 108
7
+ RodaMinorVersion = 109
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
data/lib/roda.rb CHANGED
@@ -5,6 +5,7 @@ require_relative "roda/request"
5
5
  require_relative "roda/response"
6
6
  require_relative "roda/plugins"
7
7
  require_relative "roda/cache"
8
+ require_relative "roda/opts"
8
9
  require_relative "roda/version"
9
10
 
10
11
  # The main class for Roda. Roda is built completely out of plugins, with the
@@ -18,7 +19,8 @@ class Roda
18
19
  @inherit_middleware = true
19
20
  @middleware = []
20
21
  @plugins = []
21
- @opts = {}
22
+ @opts = RodaOpts.new
23
+ @opts.roda_class = self
22
24
  @raw_route_block = nil
23
25
  @route_block = nil
24
26
  @rack_app_route_block = nil
@@ -30,6 +32,8 @@ class Roda
30
32
  module Base
31
33
  # Class methods for the Roda class.
32
34
  module ClassMethods
35
+ RodaPlugins.opt_attr_reader(self, :root, name: :app_root)
36
+
33
37
  # The rack application that this class uses.
34
38
  def app
35
39
  @app || build_rack_app
@@ -140,7 +144,7 @@ class Roda
140
144
  # Fallback to instance_exec in this case.
141
145
  b = block
142
146
  block = if RUBY_VERSION >= '2.7'
143
- eval('lambda{|*a, **kw| instance_exec(*a, **kw, &b)}', nil, __FILE__, __LINE__) # Keyword arguments fallback
147
+ lambda{|*a, **kw| instance_exec(*a, **kw, &b)} # Keyword arguments fallback
144
148
  else
145
149
  # simplecov:disable
146
150
  lambda{|*a| instance_exec(*a, &b)} # Keyword arguments fallback
@@ -189,7 +193,7 @@ class Roda
189
193
  end
190
194
 
191
195
  # Expand the given path, using the root argument as the base directory.
192
- def expand_path(path, root=opts[:root])
196
+ def expand_path(path, root=app_root)
193
197
  ::File.expand_path(path, root)
194
198
  end
195
199
 
@@ -207,9 +211,9 @@ class Roda
207
211
  # If the _roda_run_main_route instance method has not been overridden,
208
212
  # make it an alias to _roda_main_route for performance
209
213
  if instance_method(:_roda_run_main_route).owner == InstanceMethods
210
- class_eval("alias _roda_run_main_route _roda_main_route")
214
+ alias_method :_roda_run_main_route, :_roda_main_route
211
215
  end
212
- self::RodaResponse.class_eval do
216
+ self::RodaResponse.class_exec do
213
217
  if instance_method(:set_default_headers).owner == ResponseMethods &&
214
218
  instance_method(:default_headers).owner == ResponseMethods
215
219
 
@@ -253,21 +257,28 @@ class Roda
253
257
  def inherited(subclass)
254
258
  raise RodaError, "Cannot subclass a frozen Roda class" if frozen?
255
259
 
256
- # Mark current class as having been subclassed, as some optimizations
257
- # depend on the class not being subclassed
258
- opts[:subclassed] = true
259
-
260
260
  super
261
261
  subclass.instance_variable_set(:@inherit_middleware, @inherit_middleware)
262
262
  subclass.instance_variable_set(:@middleware, @inherit_middleware ? @middleware.dup : [])
263
263
  subclass.instance_variable_set(:@plugins, @plugins.dup)
264
- subclass.instance_variable_set(:@opts, opts.dup)
265
- subclass.opts.delete(:subclassed)
266
- subclass.opts.to_a.each do |k,v|
267
- if (v.is_a?(Array) || v.is_a?(Hash)) && !v.frozen?
268
- subclass.opts[k] = v.dup
264
+
265
+ opts = RodaOpts.new
266
+ opts.roda_class = subclass
267
+ @opts.each do |k,v|
268
+ opts[k] = if k == :subclassed
269
+ false
270
+ elsif (v.is_a?(Array) || v.is_a?(Hash)) && !v.frozen?
271
+ v.dup
272
+ else
273
+ v
269
274
  end
270
275
  end
276
+ subclass.instance_variable_set(:@opts, opts)
277
+
278
+ # Mark current class as having been subclassed, as some optimizations
279
+ # depend on the class not being subclassed
280
+ @opts[:subclassed] = true
281
+
271
282
  if block = @raw_route_block
272
283
  subclass.route(&block)
273
284
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.108.0
4
+ version: 3.109.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
@@ -159,11 +159,14 @@ files:
159
159
  - MIT-LICENSE
160
160
  - lib/roda.rb
161
161
  - lib/roda/cache.rb
162
+ - lib/roda/opts.rb
162
163
  - lib/roda/plugins.rb
163
164
  - lib/roda/plugins/Integer_matcher_max.rb
165
+ - lib/roda/plugins/_add_script_name.rb
164
166
  - lib/roda/plugins/_after_hook.rb
165
167
  - lib/roda/plugins/_base64.rb
166
168
  - lib/roda/plugins/_before_hook.rb
169
+ - lib/roda/plugins/_json.rb
167
170
  - lib/roda/plugins/_optimized_matching.rb
168
171
  - lib/roda/plugins/_symbol_class_matchers.rb
169
172
  - lib/roda/plugins/_symbol_regexp_matchers.rb
@@ -334,14 +337,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
334
337
  requirements:
335
338
  - - ">="
336
339
  - !ruby/object:Gem::Version
337
- version: 1.9.2
340
+ version: '2.0'
338
341
  required_rubygems_version: !ruby/object:Gem::Requirement
339
342
  requirements:
340
343
  - - ">="
341
344
  - !ruby/object:Gem::Version
342
345
  version: '0'
343
346
  requirements: []
344
- rubygems_version: 4.0.16
347
+ rubygems_version: 4.0.20
345
348
  specification_version: 4
346
349
  summary: Routing tree web toolkit
347
350
  test_files: []