roda 3.107.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 (105) 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/_base64.rb +2 -2
  6. data/lib/roda/plugins/_json.rb +22 -0
  7. data/lib/roda/plugins/_optimized_matching.rb +1 -0
  8. data/lib/roda/plugins/_symbol_class_matchers.rb +6 -6
  9. data/lib/roda/plugins/additional_render_engines.rb +5 -1
  10. data/lib/roda/plugins/additional_view_directories.rb +5 -1
  11. data/lib/roda/plugins/all_verbs.rb +2 -2
  12. data/lib/roda/plugins/assets.rb +11 -14
  13. data/lib/roda/plugins/autoload_hash_branches.rb +9 -3
  14. data/lib/roda/plugins/autoload_named_routes.rb +9 -3
  15. data/lib/roda/plugins/bearer_token.rb +2 -2
  16. data/lib/roda/plugins/break.rb +9 -1
  17. data/lib/roda/plugins/caching.rb +12 -12
  18. data/lib/roda/plugins/capture_erb.rb +5 -1
  19. data/lib/roda/plugins/chunked.rb +11 -5
  20. data/lib/roda/plugins/class_level_routing.rb +5 -3
  21. data/lib/roda/plugins/class_matchers.rb +3 -1
  22. data/lib/roda/plugins/common_logger.rb +11 -7
  23. data/lib/roda/plugins/conditional_sessions.rb +1 -1
  24. data/lib/roda/plugins/content_for.rb +5 -1
  25. data/lib/roda/plugins/content_security_policy.rb +6 -2
  26. data/lib/roda/plugins/cookie_flags.rb +8 -4
  27. data/lib/roda/plugins/cookies.rb +7 -3
  28. data/lib/roda/plugins/custom_block_results.rb +5 -3
  29. data/lib/roda/plugins/custom_matchers.rb +4 -2
  30. data/lib/roda/plugins/default_headers.rb +2 -5
  31. data/lib/roda/plugins/default_status.rb +1 -0
  32. data/lib/roda/plugins/each_part.rb +23 -27
  33. data/lib/roda/plugins/environments.rb +3 -7
  34. data/lib/roda/plugins/error_email.rb +11 -7
  35. data/lib/roda/plugins/error_handler.rb +5 -3
  36. data/lib/roda/plugins/error_mail.rb +6 -2
  37. data/lib/roda/plugins/exception_page.rb +2 -2
  38. data/lib/roda/plugins/h.rb +4 -4
  39. data/lib/roda/plugins/hash_branch_view_subdir.rb +6 -4
  40. data/lib/roda/plugins/hash_branches.rb +15 -9
  41. data/lib/roda/plugins/hash_paths.rb +11 -7
  42. data/lib/roda/plugins/hash_public.rb +13 -9
  43. data/lib/roda/plugins/hash_public_cache.rb +9 -7
  44. data/lib/roda/plugins/hash_routes.rb +5 -3
  45. data/lib/roda/plugins/heartbeat.rb +6 -2
  46. data/lib/roda/plugins/hmac_paths.rb +36 -32
  47. data/lib/roda/plugins/hooks.rb +13 -10
  48. data/lib/roda/plugins/host_authorization.rb +7 -2
  49. data/lib/roda/plugins/host_routing.rb +36 -6
  50. data/lib/roda/plugins/invalid_request_body.rb +22 -4
  51. data/lib/roda/plugins/ip_from_header.rb +5 -1
  52. data/lib/roda/plugins/json.rb +10 -8
  53. data/lib/roda/plugins/json_parser.rb +43 -11
  54. data/lib/roda/plugins/mail_processor.rb +11 -8
  55. data/lib/roda/plugins/mailer.rb +9 -7
  56. data/lib/roda/plugins/match_affix.rb +6 -1
  57. data/lib/roda/plugins/match_hook_args.rb +7 -5
  58. data/lib/roda/plugins/middleware.rb +13 -7
  59. data/lib/roda/plugins/module_include.rb +1 -1
  60. data/lib/roda/plugins/multi_public.rb +6 -2
  61. data/lib/roda/plugins/multi_route.rb +1 -1
  62. data/lib/roda/plugins/multi_run.rb +13 -17
  63. data/lib/roda/plugins/named_routes.rb +8 -6
  64. data/lib/roda/plugins/named_templates.rb +5 -3
  65. data/lib/roda/plugins/optimized_segment_matchers.rb +13 -6
  66. data/lib/roda/plugins/optimized_string_matchers.rb +6 -3
  67. data/lib/roda/plugins/params_capturing.rb +16 -7
  68. data/lib/roda/plugins/params_capturing_restore.rb +72 -0
  69. data/lib/roda/plugins/part.rb +4 -4
  70. data/lib/roda/plugins/pass.rb +4 -1
  71. data/lib/roda/plugins/path.rb +22 -20
  72. data/lib/roda/plugins/path_matchers.rb +5 -1
  73. data/lib/roda/plugins/path_rewriter.rb +8 -5
  74. data/lib/roda/plugins/permissions_policy.rb +7 -3
  75. data/lib/roda/plugins/public.rb +15 -14
  76. data/lib/roda/plugins/recheck_precompiled_assets.rb +1 -1
  77. data/lib/roda/plugins/redirect_http_to_https.rb +6 -3
  78. data/lib/roda/plugins/render.rb +58 -61
  79. data/lib/roda/plugins/render_coverage.rb +12 -8
  80. data/lib/roda/plugins/render_each.rb +2 -2
  81. data/lib/roda/plugins/render_locals.rb +12 -6
  82. data/lib/roda/plugins/request_aref.rb +1 -1
  83. data/lib/roda/plugins/response_content_type.rb +7 -3
  84. data/lib/roda/plugins/route_csrf.rb +42 -5
  85. data/lib/roda/plugins/run_append_slash.rb +12 -4
  86. data/lib/roda/plugins/sec_fetch_site_csrf.rb +11 -4
  87. data/lib/roda/plugins/sessions.rb +17 -13
  88. data/lib/roda/plugins/shape_friendly.rb +7 -5
  89. data/lib/roda/plugins/sinatra_helpers.rb +7 -3
  90. data/lib/roda/plugins/static.rb +3 -0
  91. data/lib/roda/plugins/status_handler.rb +5 -3
  92. data/lib/roda/plugins/strip_path_prefix.rb +4 -2
  93. data/lib/roda/plugins/symbol_matchers.rb +3 -1
  94. data/lib/roda/plugins/symbol_views.rb +1 -1
  95. data/lib/roda/plugins/timestamp_public.rb +8 -4
  96. data/lib/roda/plugins/type_routing.rb +10 -6
  97. data/lib/roda/plugins/typecast_params.rb +2 -2
  98. data/lib/roda/plugins/typecast_params_sized_integers.rb +1 -1
  99. data/lib/roda/plugins/url_escape.rb +2 -14
  100. data/lib/roda/plugins/view_options.rb +3 -5
  101. data/lib/roda/plugins.rb +14 -4
  102. data/lib/roda/request.rb +25 -9
  103. data/lib/roda/version.rb +1 -1
  104. data/lib/roda.rb +32 -21
  105. metadata +7 -3
@@ -289,6 +289,10 @@ class Roda
289
289
  policy.freeze
290
290
  end
291
291
 
292
+ module ClassMethods
293
+ RodaPlugins.opt_attr_reader(self, :content_security_policy)
294
+ end
295
+
292
296
  module InstanceMethods
293
297
  # If a block is given, yield the current content security policy. Returns the
294
298
  # current content security policy.
@@ -308,7 +312,7 @@ class Roda
308
312
 
309
313
  # The current content security policy to be used for this response.
310
314
  def content_security_policy
311
- @content_security_policy ||= roda_class.opts[:content_security_policy].dup
315
+ @content_security_policy ||= roda_class.content_security_policy.dup
312
316
  end
313
317
 
314
318
  # Do not set a content security policy header for this response.
@@ -322,7 +326,7 @@ class Roda
322
326
  def set_default_headers
323
327
  super
324
328
  unless @skip_content_security_policy
325
- (@content_security_policy || roda_class.opts[:content_security_policy]).set_header(headers)
329
+ (@content_security_policy || roda_class.content_security_policy).set_header(headers)
326
330
  end
327
331
  end
328
332
  end
@@ -48,9 +48,9 @@ class Roda
48
48
  # Note that this plugin only affects cookies set by the application, and does not
49
49
  # affect cookies set by middleware the application is using.
50
50
  module CookieFlags
51
- # :nocov:
51
+ # simplecov:disable
52
52
  MATCH_METH = RUBY_VERSION >= '2.4' ? :match? : :match
53
- # :nocov:
53
+ # simplecov:enable
54
54
  private_constant :MATCH_METH
55
55
 
56
56
  DEFAULTS = {:secure=>true, :httponly=>true, :same_site=>'strict', :action=>:modify}.freeze
@@ -61,7 +61,7 @@ class Roda
61
61
  end
62
62
 
63
63
  def self.configure(app, opts=OPTS)
64
- previous = app.opts[:cookie_flags] || DEFAULTS
64
+ previous = app.cookie_flags || DEFAULTS
65
65
  opts = app.opts[:cookie_flags] = previous.merge(opts)
66
66
 
67
67
  case opts[:same_site]
@@ -74,11 +74,15 @@ class Roda
74
74
  opts.freeze
75
75
  end
76
76
 
77
+ module ClassMethods
78
+ RodaPlugins.opt_attr_reader(self, :cookie_flags)
79
+ end
80
+
77
81
  module InstanceMethods
78
82
  private
79
83
 
80
84
  def _handle_cookie_flags_array(cookies)
81
- opts = self.class.opts[:cookie_flags]
85
+ opts = self.class.cookie_flags
82
86
  needs_secure = opts[:secure]
83
87
  needs_httponly = opts[:httponly]
84
88
  if needs_same_site = opts[:same_site]
@@ -20,7 +20,11 @@ class Roda
20
20
  module Cookies
21
21
  # Allow setting default cookie options when loading the cookies plugin.
22
22
  def self.configure(app, opts={})
23
- app.opts[:cookies_opts] = (app.opts[:cookies_opts]||{}).merge(opts).freeze
23
+ app.opts[:cookies_opts] = (app.cookies_opts||{}).merge(opts).freeze
24
+ end
25
+
26
+ module ClassMethods
27
+ RodaPlugins.opt_attr_reader(self, :cookies_opts)
24
28
  end
25
29
 
26
30
  module ResponseMethods
@@ -32,7 +36,7 @@ class Roda
32
36
  # response.delete_cookie('foo')
33
37
  # response.delete_cookie('foo', domain: 'example.org')
34
38
  def delete_cookie(key, value = {})
35
- ::Rack::Utils.delete_cookie_header!(@headers, key, roda_class.opts[:cookies_opts].merge(value))
39
+ ::Rack::Utils.delete_cookie_header!(@headers, key, roda_class.cookies_opts.merge(value))
36
40
  end
37
41
 
38
42
  # Set the cookie with the given key in the headers.
@@ -41,7 +45,7 @@ class Roda
41
45
  # response.set_cookie('foo', value: 'bar', domain: 'example.org')
42
46
  def set_cookie(key, value)
43
47
  value = { :value=>value } unless value.respond_to?(:keys)
44
- ::Rack::Utils.set_cookie_header!(@headers, key, roda_class.opts[:cookies_opts].merge(value))
48
+ ::Rack::Utils.set_cookie_header!(@headers, key, roda_class.cookies_opts.merge(value))
45
49
  end
46
50
  end
47
51
  end
@@ -44,16 +44,18 @@ class Roda
44
44
  end
45
45
 
46
46
  module ClassMethods
47
+ RodaPlugins.opt_attr_reader(self, :custom_block_results)
48
+
47
49
  # Freeze the configured custom block results when freezing the app.
48
50
  def freeze
49
- opts[:custom_block_results].freeze
51
+ custom_block_results.freeze
50
52
  super
51
53
  end
52
54
 
53
55
  # Specify a block that will be called when an instance of klass
54
56
  # is returned as a block result. The block defines a method.
55
57
  def handle_block_result(klass, &block)
56
- opts[:custom_block_results][klass] = define_roda_method(opts[:custom_block_results][klass] || "custom_block_result_#{klass}", 1, &block)
58
+ custom_block_results[klass] = define_roda_method(custom_block_results[klass] || "custom_block_result_#{klass}", 1, &block)
57
59
  end
58
60
  end
59
61
 
@@ -63,7 +65,7 @@ class Roda
63
65
  # Try each configured custom block result, and call the related method
64
66
  # to get the block result.
65
67
  def unsupported_block_result(result)
66
- roda_class.opts[:custom_block_results].each do |klass, meth|
68
+ roda_class.custom_block_results.each do |klass, meth|
67
69
  if klass === result
68
70
  result = scope.send(meth, result)
69
71
 
@@ -58,8 +58,10 @@ class Roda
58
58
  end
59
59
 
60
60
  module ClassMethods
61
+ RodaPlugins.opt_attr_reader(self, :custom_matchers)
62
+
61
63
  def custom_matcher(match_class, &block)
62
- custom_matchers = Hash[opts[:custom_matchers]]
64
+ custom_matchers = Hash[self.custom_matchers]
63
65
  meth = custom_matchers[match_class] = custom_matchers[match_class] || :"_custom_matcher_#{match_class}"
64
66
  opts[:custom_matchers] = custom_matchers.freeze
65
67
  self::RodaRequest.send(:define_method, meth, &block)
@@ -72,7 +74,7 @@ class Roda
72
74
 
73
75
  # Try custom matchers before calling super
74
76
  def unsupported_matcher(matcher)
75
- roda_class.opts[:custom_matchers].each do |match_class, meth|
77
+ roda_class.custom_matchers.each do |match_class, meth|
76
78
  if match_class === matcher
77
79
  return send(meth, matcher)
78
80
  end
@@ -27,15 +27,12 @@ class Roda
27
27
  end
28
28
 
29
29
  module ClassMethods
30
- # The default response headers to use for the current class.
31
- def default_headers
32
- opts[:default_headers]
33
- end
30
+ RodaPlugins.opt_attr_reader(self, :default_headers)
34
31
 
35
32
  # Optimize the response class set_default_headers method if it hasn't been
36
33
  # overridden and all default headers are strings.
37
34
  def freeze
38
- if (headers = opts[:default_headers]).all?{|k, v| k.is_a?(String) && v.is_a?(String)} &&
35
+ if (headers = default_headers).all?{|k, v| k.is_a?(String) && v.is_a?(String)} &&
39
36
  (self::RodaResponse.instance_method(:set_default_headers).owner == Base::ResponseMethods)
40
37
  self::RodaResponse.class_eval(<<-END, __FILE__, __LINE__+1)
41
38
  private
@@ -18,6 +18,7 @@ class Roda
18
18
  def self.configure(app, &block)
19
19
  raise RodaError, "default_status plugin requires a block" unless block
20
20
  if check_arity = app.opts.fetch(:check_arity, true)
21
+ # RODA4: Remove support for :check_arity, default to false behavior
21
22
  unless block.arity == 0
22
23
  if check_arity == :warn
23
24
  RodaPlugins.warn "Arity mismatch in block passed to plugin :default_status. Expected Arity 0, but arguments required for #{block.inspect}"
@@ -38,46 +38,42 @@ class Roda
38
38
 
39
39
  module InstanceMethods
40
40
  if RUBY_VERSION >= '2.7'
41
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
42
- def each_part(enum, template, **locals, &block)
43
- render_each(enum, template, :locals=>locals, &block)
44
- end
45
- RUBY
46
- # :nocov:
41
+ def each_part(enum, template, **locals, &block)
42
+ render_each(enum, template, :locals=>locals, &block)
43
+ end
44
+ # simplecov:disable
47
45
  else
48
46
  def each_part(enum, template, locals=OPTS, &block)
49
47
  render_each(enum, template, :locals=>locals, &block)
50
48
  end
51
49
  end
52
- # :nocov:
50
+ # simplecov:enable
53
51
  end
54
52
 
55
53
  module AssumeFixedLocalsInstanceMethods
56
- # :nocov:
54
+ # simplecov:disable
57
55
  if RUBY_VERSION >= '3.0'
58
- # :nocov:
59
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
60
- def each_part(enum, template, **locals, &block)
61
- if optimized_method = _cached_render_each_template_method(template)
62
- optimized_method = optimized_method[0]
63
- as = render_each_default_local(template)
64
- if defined?(yield)
65
- enum.each do |v|
66
- locals[as] = v
67
- yield send(optimized_method, **locals)
68
- end
69
- nil
70
- else
71
- enum.map do |v|
72
- locals[as] = v
73
- send(optimized_method, **locals)
74
- end.join
56
+ # simplecov:enable
57
+ def each_part(enum, template, **locals, &block)
58
+ if optimized_method = _cached_render_each_template_method(template)
59
+ optimized_method = optimized_method[0]
60
+ as = render_each_default_local(template)
61
+ if defined?(yield)
62
+ enum.each do |v|
63
+ locals[as] = v
64
+ yield send(optimized_method, **locals)
75
65
  end
66
+ nil
76
67
  else
77
- render_each(enum, template, :locals=>locals, &block)
68
+ enum.map do |v|
69
+ locals[as] = v
70
+ send(optimized_method, **locals)
71
+ end.join
78
72
  end
73
+ else
74
+ render_each(enum, template, :locals=>locals, &block)
79
75
  end
80
- RUBY
76
+ end
81
77
  end
82
78
  end
83
79
  end
@@ -44,10 +44,12 @@ class Roda
44
44
  # if no environment is given. If ENV['RACK_ENV'] is not set and
45
45
  # no environment is given, assume the development environment.
46
46
  def self.configure(app, env=ENV["RACK_ENV"])
47
- app.environment = (env || 'development').to_sym
47
+ app.opts[:environment] = (env || 'development').to_sym
48
48
  end
49
49
 
50
50
  module ClassMethods
51
+ RodaPlugins.opt_attr_reader(self, :environment)
52
+
51
53
  # If no environments are given or one of the given environments
52
54
  # matches the current environment, yield the receiver to the block.
53
55
  def configure(*envs)
@@ -56,12 +58,6 @@ class Roda
56
58
  end
57
59
  end
58
60
 
59
- # The current environment for the application, which should be stored
60
- # as a symbol.
61
- def environment
62
- opts[:environment]
63
- end
64
-
65
61
  # Override the environment for the application, instead of using
66
62
  # RACK_ENV.
67
63
  def environment=(v)
@@ -44,9 +44,9 @@ class Roda
44
44
  :filter=>lambda{|k,v| false},
45
45
  :headers=>OPTS,
46
46
  :host=>'localhost',
47
- # :nocov:
47
+ # simplecov:disable
48
48
  :emailer=>lambda{|h| Net::SMTP.start(h[:host]){|s| s.send_message(h[:message], h[:from], h[:to])}},
49
- # :nocov:
49
+ # simplecov:enable
50
50
  :default_headers=>lambda do |h, e|
51
51
  subject = if e.respond_to?(:message)
52
52
  "#{e.class}: #{e.message}"
@@ -56,7 +56,7 @@ class Roda
56
56
  {'From'=>h[:from], 'To'=>h[:to], 'Subject'=>"#{h[:prefix]}#{subject}"}
57
57
  end,
58
58
  :body=>lambda do |s, e|
59
- filter = s.opts[:error_email][:filter]
59
+ filter = s.class.error_email_opts[:filter]
60
60
  format = lambda do |h|
61
61
  h = h.map{|k, v| "#{k.inspect} => #{filter.call(k, v) ? 'FILTERED' : v.inspect}"}
62
62
  h.sort!
@@ -120,8 +120,12 @@ END
120
120
  raise RodaError, "must provide :to and :from options to error_email plugin"
121
121
  end
122
122
  app.opts[:error_email] = email_opts
123
- app.opts[:error_email][:headers].freeze
124
- app.opts[:error_email].freeze
123
+ app.error_email_opts[:headers].freeze
124
+ app.error_email_opts.freeze
125
+ end
126
+
127
+ module ClassMethods
128
+ RodaPlugins.opt_attr_reader(self, :error_email, name: :error_email_opts)
125
129
  end
126
130
 
127
131
  module InstanceMethods
@@ -129,7 +133,7 @@ END
129
133
  # instance, but it can be a plain string which is used as the subject for
130
134
  # the email.
131
135
  def error_email(exception)
132
- email_opts = self.class.opts[:error_email].dup
136
+ email_opts = self.class.error_email_opts.dup
133
137
  email_opts[:message] = error_email_content(exception)
134
138
  email_opts[:emailer].call(email_opts)
135
139
  end
@@ -137,7 +141,7 @@ END
137
141
  # The content of the email to send, include the headers and the body.
138
142
  # Takes the same argument as #error_email.
139
143
  def error_email_content(exception)
140
- email_opts = self.class.opts[:error_email]
144
+ email_opts = self.class.error_email_opts
141
145
  headers = email_opts[:default_headers].call(email_opts, exception)
142
146
  headers = headers.merge(email_opts[:headers])
143
147
  headers = headers.map{|k,v| "#{k}: #{v.gsub(/\r?\n/m, "\r\n ")}"}.sort.join("\r\n")
@@ -49,7 +49,7 @@ class Roda
49
49
  # If a block is given, automatically call the +error+ method on
50
50
  # the Roda class with it.
51
51
  def self.configure(app, opts={}, &block)
52
- app.opts[:error_handler_classes] = (opts[:classes] || app.opts[:error_handler_classes] || DEFAULT_ERROR_HANDLER_CLASSES).dup.freeze
52
+ app.opts[:error_handler_classes] = (opts[:classes] || app.error_handler_classes || DEFAULT_ERROR_HANDLER_CLASSES).dup.freeze
53
53
 
54
54
  if block
55
55
  app.error(&block)
@@ -57,6 +57,8 @@ class Roda
57
57
  end
58
58
 
59
59
  module ClassMethods
60
+ RodaPlugins.opt_attr_reader(self, :error_handler_classes)
61
+
60
62
  # Install the given block as the error handler, so that if routing
61
63
  # the request raises an exception, the block will be called with
62
64
  # the exception in the scope of the Roda instance.
@@ -77,7 +79,7 @@ class Roda
77
79
  ensure
78
80
  _roda_after(res)
79
81
  end
80
- rescue *opts[:error_handler_classes] => e
82
+ rescue *self.class.error_handler_classes => e
81
83
  _handle_error(e)
82
84
  end
83
85
 
@@ -89,7 +91,7 @@ class Roda
89
91
  ensure
90
92
  _roda_after(res)
91
93
  end
92
- rescue *opts[:error_handler_classes] => e
94
+ rescue *self.class.error_handler_classes => e
93
95
  _handle_error(e)
94
96
  end
95
97
 
@@ -44,12 +44,16 @@ class Roda
44
44
 
45
45
  # Set default opts for plugin. See ErrorEmail module RDoc for options.
46
46
  def self.configure(app, opts=OPTS)
47
- app.opts[:error_mail] = email_opts = (app.opts[:error_mail] || {:filter=>DEFAULT_FILTER}).merge(opts).freeze
47
+ app.opts[:error_mail] = email_opts = (app.error_mail_opts || {:filter=>DEFAULT_FILTER}).merge(opts).freeze
48
48
  unless email_opts[:to] && email_opts[:from]
49
49
  raise RodaError, "must provide :to and :from options to error_mail plugin"
50
50
  end
51
51
  end
52
52
 
53
+ module ClassMethods
54
+ RodaPlugins.opt_attr_reader(self, :error_mail, name: :error_mail_opts)
55
+ end
56
+
53
57
  module InstanceMethods
54
58
  # Send an email for the given error. +exception+ is usually an exception
55
59
  # instance, but it can be a plain string which is used as the subject for
@@ -67,7 +71,7 @@ class Roda
67
71
  private
68
72
 
69
73
  def _error_mail(e)
70
- email_opts = self.class.opts[:error_mail]
74
+ email_opts = self.class.error_mail_opts
71
75
  subject = if e.respond_to?(:message)
72
76
  "#{e.class}: #{e.message}"
73
77
  else
@@ -415,14 +415,14 @@ END
415
415
  def exception_page_exception_message(exception)
416
416
  exception.detailed_message(highlight: false).to_s
417
417
  end
418
- # :nocov:
418
+ # simplecov:disable
419
419
  else
420
420
  # Return message to use for exception.
421
421
  def exception_page_exception_message(exception)
422
422
  exception.message.to_s
423
423
  end
424
424
  end
425
- # :nocov:
425
+ # simplecov:enable
426
426
  end
427
427
 
428
428
  module RequestMethods
@@ -17,10 +17,10 @@ class Roda
17
17
  begin
18
18
  require 'cgi/escape'
19
19
  unless CGI.respond_to?(:escapeHTML) # work around for JRuby 9.1
20
- # :nocov:
20
+ # simplecov:disable
21
21
  CGI = Object.new
22
22
  CGI.extend(defined?(::CGI::Escape) ? ::CGI::Escape : ::CGI::Util)
23
- # :nocov:
23
+ # simplecov:enable
24
24
  end
25
25
 
26
26
  module InstanceMethods
@@ -30,7 +30,7 @@ class Roda
30
30
  end
31
31
  end
32
32
  rescue LoadError
33
- # :nocov:
33
+ # simplecov:disable
34
34
 
35
35
  # A Hash of entities and their escaped equivalents,
36
36
  # to be escaped by h().
@@ -50,7 +50,7 @@ class Roda
50
50
  string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
51
51
  end
52
52
  end
53
- # :nocov:
53
+ # simplecov:enable
54
54
  end
55
55
  end
56
56
 
@@ -33,9 +33,11 @@ class Roda
33
33
  end
34
34
 
35
35
  module ClassMethods
36
+ RodaPlugins.opt_attr_reader(self, :hash_branch_view_subdir_methods)
37
+
36
38
  # Freeze the hash_branch_view_subdir metadata when freezing the app.
37
39
  def freeze
38
- opts[:hash_branch_view_subdir_methods].freeze.each_value(&:freeze)
40
+ hash_branch_view_subdir_methods.freeze.each_value(&:freeze)
39
41
  super
40
42
  end
41
43
 
@@ -43,8 +45,8 @@ class Roda
43
45
  def inherited(subclass)
44
46
  super
45
47
 
46
- h = subclass.opts[:hash_branch_view_subdir_methods]
47
- opts[:hash_branch_view_subdir_methods].each do |namespace, routes|
48
+ h = subclass.hash_branch_view_subdir_methods
49
+ hash_branch_view_subdir_methods.each do |namespace, routes|
48
50
  h[namespace] = routes.dup
49
51
  end
50
52
  end
@@ -53,7 +55,7 @@ class Roda
53
55
  # by modifying the generated method to append the view subdirectory before
54
56
  # dispatching to the original block.
55
57
  def hash_branch(namespace='', segment, &block)
56
- meths = opts[:hash_branch_view_subdir_methods][namespace] ||= {}
58
+ meths = hash_branch_view_subdir_methods[namespace] ||= {}
57
59
 
58
60
  if block
59
61
  meth = meths[segment] = define_roda_method(meths[segment] || "_hash_branch_view_subdir_#{namespace}_#{segment}", 1, &convert_route_block(block))
@@ -88,9 +88,11 @@ class Roda
88
88
  end
89
89
 
90
90
  module ClassMethods
91
+ RodaPlugins.opt_attr_reader(self, :hash_branches)
92
+
91
93
  # Freeze the hash_branches metadata when freezing the app.
92
94
  def freeze
93
- opts[:hash_branches].freeze.each_value(&:freeze)
95
+ hash_branches.freeze.each_value(&:freeze)
94
96
  super
95
97
  end
96
98
 
@@ -98,8 +100,8 @@ class Roda
98
100
  def inherited(subclass)
99
101
  super
100
102
 
101
- h = subclass.opts[:hash_branches]
102
- opts[:hash_branches].each do |namespace, routes|
103
+ h = subclass.hash_branches
104
+ hash_branches.each do |namespace, routes|
103
105
  h[namespace] = routes.dup
104
106
  end
105
107
  end
@@ -108,7 +110,7 @@ class Roda
108
110
  # a block, removes the existing branch handler if it exists.
109
111
  def hash_branch(namespace='', segment, &block)
110
112
  segment = "/#{segment}"
111
- routes = opts[:hash_branches][namespace] ||= {}
113
+ routes = hash_branches[namespace] ||= {}
112
114
  if block
113
115
  routes[segment] = define_roda_method(routes[segment] || "hash_branch_#{namespace}_#{segment}", 1, &convert_route_block(block))
114
116
  elsif meth = routes.delete(segment)
@@ -125,15 +127,19 @@ class Roda
125
127
 
126
128
  return unless rp.getbyte(0) == 47 # "/"
127
129
 
128
- if routes = roda_class.opts[:hash_branches][namespace]
130
+ if routes = roda_class.hash_branches[namespace]
129
131
  if segment_end = rp.index('/', 1)
130
132
  if meth = routes[rp[0, segment_end]]
131
- @remaining_path = rp[segment_end, 100000000]
132
- always{scope.send(meth, self)}
133
+ always do
134
+ @remaining_path = rp[segment_end, 100000000]
135
+ scope.send(meth, self)
136
+ end
133
137
  end
134
138
  elsif meth = routes[rp]
135
- @remaining_path = ''
136
- always{scope.send(meth, self)}
139
+ always do
140
+ @remaining_path = ''
141
+ scope.send(meth, self)
142
+ end
137
143
  end
138
144
  end
139
145
  end
@@ -80,9 +80,11 @@ class Roda
80
80
  end
81
81
 
82
82
  module ClassMethods
83
+ RodaPlugins.opt_attr_reader(self, :hash_paths)
84
+
83
85
  # Freeze the hash_paths metadata when freezing the app.
84
86
  def freeze
85
- opts[:hash_paths].freeze.each_value(&:freeze)
87
+ hash_paths.freeze.each_value(&:freeze)
86
88
  super
87
89
  end
88
90
 
@@ -90,8 +92,8 @@ class Roda
90
92
  def inherited(subclass)
91
93
  super
92
94
 
93
- h = subclass.opts[:hash_paths]
94
- opts[:hash_paths].each do |namespace, routes|
95
+ h = subclass.hash_paths
96
+ hash_paths.each do |namespace, routes|
95
97
  h[namespace] = routes.dup
96
98
  end
97
99
  end
@@ -102,7 +104,7 @@ class Roda
102
104
  # there is one. If called without a block, removes the existing
103
105
  # path handler if it exists.
104
106
  def hash_path(namespace='', path, &block)
105
- routes = opts[:hash_paths][namespace] ||= {}
107
+ routes = hash_paths[namespace] ||= {}
106
108
  if block
107
109
  routes[path] = define_roda_method(routes[path] || "hash_path_#{namespace}_#{path}", 1, &convert_route_block(block))
108
110
  elsif meth = routes.delete(path)
@@ -115,9 +117,11 @@ class Roda
115
117
  # Checks the matching hash_path namespace for a branch matching the
116
118
  # remaining path, and dispatch to that block if there is one.
117
119
  def hash_paths(namespace=matched_path)
118
- if (routes = roda_class.opts[:hash_paths][namespace]) && (meth = routes[@remaining_path])
119
- @remaining_path = ''
120
- always{scope.send(meth, self)}
120
+ if (routes = roda_class.hash_paths[namespace]) && (meth = routes[@remaining_path])
121
+ always do
122
+ @remaining_path = ''
123
+ scope.send(meth, self)
124
+ end
121
125
  end
122
126
  end
123
127
  end
@@ -51,11 +51,11 @@ class Roda
51
51
  Digest = begin
52
52
  require 'openssl'
53
53
  ::OpenSSL::Digest
54
- # :nocov:
54
+ # simplecov:disable
55
55
  rescue LoadError
56
56
  require 'digest/sha2'
57
57
  ::Digest
58
- # :nocov:
58
+ # simplecov:enable
59
59
  end
60
60
 
61
61
  def self.load_dependencies(app, opts = OPTS)
@@ -78,16 +78,20 @@ class Roda
78
78
  end
79
79
 
80
80
  module ClassMethods
81
+ RodaPlugins.opt_attr_reader(self, :hash_public_prefix)
82
+ RodaPlugins.opt_attr_reader(self, :hash_public_length)
83
+ RodaPlugins.opt_attr_reader(self, :hash_public_mutex)
84
+ RodaPlugins.opt_attr_reader(self, :hash_public_cache)
85
+
81
86
  # The digest for the given file to use in hash_path.
82
87
  def hash_path_digest(file)
83
- opts = self.opts
84
- cache = opts[:hash_public_cache]
85
- mutex = opts[:hash_public_mutex]
88
+ cache = hash_public_cache
89
+ mutex = hash_public_mutex
86
90
  unless digest = mutex.synchronize{cache[file]}
87
- digest = Digest::SHA256.file(File.join(opts[:public_root], file)).base64digest
91
+ digest = Digest::SHA256.file(File.join(public_root, file)).base64digest
88
92
  digest.chomp!("=")
89
93
  digest.tr!("+/", "-_")
90
- if length = opts[:hash_public_length]
94
+ if length = hash_public_length
91
95
  digest = digest[0, length]
92
96
  end
93
97
  digest.freeze
@@ -102,7 +106,7 @@ class Roda
102
106
  # This does not check the file is inside the directory for performance
103
107
  # reasons, so this should not be called with untrusted input.
104
108
  def hash_path(file)
105
- "/#{opts[:hash_public_prefix]}/#{self.class.hash_path_digest(file)}/#{file}"
109
+ "/#{self.class.hash_public_prefix}/#{self.class.hash_path_digest(file)}/#{file}"
106
110
  end
107
111
  end
108
112
 
@@ -112,7 +116,7 @@ class Roda
112
116
  # a string segment for the content hash, and this is a GET request.
113
117
  def hash_public
114
118
  if is_get?
115
- on roda_class.opts[:hash_public_prefix], String do |_|
119
+ on roda_class.hash_public_prefix, String do |_|
116
120
  public
117
121
  end
118
122
  end