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
@@ -20,11 +20,15 @@ class Roda
20
20
  app.opts[:ip_from_header_env_key] = "HTTP_#{header.upcase.tr('-', '_')}".freeze
21
21
  end
22
22
 
23
+ module ClassMethods
24
+ RodaPlugins.opt_attr_reader(self, :ip_from_header_env_key)
25
+ end
26
+
23
27
  module RequestMethods
24
28
  # Return the IP address continained in the configured header, if present.
25
29
  # Fallback to the default behavior if not present.
26
30
  def ip
27
- @env[roda_class.opts[:ip_from_header_env_key]] || super
31
+ @env[roda_class.ip_from_header_env_key] || super
28
32
  end
29
33
  end
30
34
  end
@@ -1,7 +1,5 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require 'json'
4
-
5
3
  class Roda
6
4
  module RodaPlugins
7
5
  # The json plugin allows match blocks to return
@@ -57,6 +55,7 @@ class Roda
57
55
  # not support treating String, FalseClass, or NilClass values as JSON.
58
56
  module Json
59
57
  def self.load_dependencies(app, opts=OPTS)
58
+ app.plugin :_json
60
59
  app.plugin :custom_block_results
61
60
  end
62
61
 
@@ -69,10 +68,10 @@ class Roda
69
68
  classes.uniq!
70
69
  classes.freeze
71
70
  classes.each do |klass|
72
- app.opts[:custom_block_results][klass] = :handle_json_block_result
71
+ app.custom_block_results[klass] = :handle_json_block_result
73
72
  end
74
73
 
75
- app.opts[:json_result_serializer] = opts[:serializer] || app.opts[:json_result_serializer] || app.opts[:json_serializer] || :to_json.to_proc
74
+ app.opts[:json_result_serializer] = opts[:serializer] || app.json_result_serializer || app.json_serializer
76
75
 
77
76
  app.opts[:json_result_include_request] = opts[:include_request] if opts.has_key?(:include_request)
78
77
 
@@ -80,6 +79,10 @@ class Roda
80
79
  end
81
80
 
82
81
  module ClassMethods
82
+ RodaPlugins.opt_attr_reader(self, :json_result_serializer)
83
+ RodaPlugins.opt_attr_reader(self, :json_result_include_request, name: :json_result_include_request?)
84
+ RodaPlugins.opt_attr_reader(self, :json_result_content_type)
85
+
83
86
  # The classes that should be automatically converted to json
84
87
  def json_result_classes
85
88
  # RODA4: remove, only used by previous implementation.
@@ -91,7 +94,7 @@ class Roda
91
94
  # Handle a result for one of the registered JSON result classes
92
95
  # by converting the result to JSON.
93
96
  def handle_json_block_result(result)
94
- @_response[RodaResponseHeaders::CONTENT_TYPE] ||= opts[:json_result_content_type]
97
+ @_response[RodaResponseHeaders::CONTENT_TYPE] ||= self.class.json_result_content_type
95
98
  @_request.send(:convert_to_json, result)
96
99
  end
97
100
  end
@@ -102,10 +105,9 @@ class Roda
102
105
  # Convert the given object to JSON. Uses to_json by default,
103
106
  # but can use a custom serializer passed to the plugin.
104
107
  def convert_to_json(result)
105
- opts = roda_class.opts
106
- serializer = opts[:json_result_serializer]
108
+ serializer = roda_class.json_result_serializer
107
109
 
108
- if opts[:json_result_include_request]
110
+ if roda_class.json_result_include_request?
109
111
  serializer.call(result, self)
110
112
  else
111
113
  serializer.call(result)
@@ -1,7 +1,5 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require 'json'
4
-
5
3
  class Roda
6
4
  module RodaPlugins
7
5
  # The json_parser plugin parses request bodies in JSON format
@@ -22,6 +20,10 @@ class Roda
22
20
  # simplecov:enable
23
21
  private_constant :MATCH_METHOD
24
22
 
23
+ def self.load_dependencies(app, opts=OPTS)
24
+ app.plugin :_json
25
+ end
26
+
25
27
  # Handle options for the json_parser plugin:
26
28
  # :content_type_regexp :: A regexp used to determine if the request's
27
29
  # content type is JSON. For backwards compatibility,
@@ -46,12 +48,12 @@ class Roda
46
48
  # :always will wrap all values, and a value of :unless_hash will
47
49
  # only wrap values that are not already hashes.
48
50
  def self.configure(app, opts=OPTS)
49
- app.opts[:json_parser_error_handler] = opts[:error_handler] || app.opts[:json_parser_error_handler] || DEFAULT_ERROR_HANDLER
50
- app.opts[:json_parser_parser] = opts[:parser] || app.opts[:json_parser_parser] || app.opts[:json_parser] || JSON.method(:parse)
51
+ app.opts[:json_parser_error_handler] = opts[:error_handler] || app.json_parser_error_handler || DEFAULT_ERROR_HANDLER
52
+ app.opts[:json_parser_parser] = opts[:parser] || app.json_parser_parser || app.json_parser
51
53
  app.opts[:json_parser_include_request] = opts[:include_request] if opts.has_key?(:include_request)
52
- app.opts[:json_parser_content_type_regexp] = opts[:content_type_regexp] || app.opts[:json_parser_content_type_regexp]
54
+ app.opts[:json_parser_content_type_regexp] = opts[:content_type_regexp] || app.json_parser_content_type_regexp
53
55
 
54
- unless app.opts[:json_parser_content_type_regexp]
56
+ unless app.json_parser_content_type_regexp
55
57
  # RODA4: Switch to /\Aapplication\/(?:vnd\.api\+)?json\b/i by default
56
58
  RodaPlugins.warn(':content_type_regexp option not provided to json_parser plugin, using /json/ for backwards compatibility, which may be insecure, using /\Aapplication\/json\b/i or /\Aapplication\/(?:vnd\.api\+)?json\b/i is recommended')
57
59
  app.opts[:json_parser_content_type_regexp] = /json/
@@ -67,6 +69,14 @@ class Roda
67
69
  end
68
70
  end
69
71
 
72
+ module ClassMethods
73
+ RodaPlugins.opt_attr_reader(self, :json_parser_error_handler)
74
+ RodaPlugins.opt_attr_reader(self, :json_parser_parser)
75
+ RodaPlugins.opt_attr_reader(self, :json_parser_include_request, name: :json_parser_include_request?)
76
+ RodaPlugins.opt_attr_reader(self, :json_parser_content_type_regexp)
77
+ RodaPlugins.opt_attr_reader(self, :json_parser_wrap)
78
+ end
79
+
70
80
  module RequestMethods
71
81
  # If the Content-Type header in the request includes "json",
72
82
  # parse the request body as JSON. Ignore an empty request body.
@@ -76,7 +86,7 @@ class Roda
76
86
  return post_params
77
87
  end
78
88
 
79
- unless (input = env["rack.input"]) && (content_type = self.content_type) && roda_class.opts[:json_parser_content_type_regexp].send(MATCH_METHOD, content_type)
89
+ unless (input = env["rack.input"]) && (content_type = self.content_type) && roda_class.json_parser_content_type_regexp.send(MATCH_METHOD, content_type)
80
90
  return super
81
91
  end
82
92
 
@@ -85,10 +95,10 @@ class Roda
85
95
  begin
86
96
  json_params = parse_json(str)
87
97
  rescue
88
- roda_class.opts[:json_parser_error_handler].call(self)
98
+ roda_class.json_parser_error_handler.call(self)
89
99
  end
90
100
 
91
- wrap = roda_class.opts[:json_parser_wrap]
101
+ wrap = roda_class.json_parser_wrap
92
102
  if wrap == :always || (wrap == :unless_hash && !json_params.is_a?(Hash))
93
103
  json_params = {"_json"=>json_params}
94
104
  end
@@ -99,9 +109,13 @@ class Roda
99
109
  private
100
110
 
101
111
  def parse_json(str)
102
- args = [str]
103
- args << self if roda_class.opts[:json_parser_include_request]
104
- roda_class.opts[:json_parser_parser].call(*args)
112
+ parser = roda_class.json_parser_parser
113
+
114
+ if roda_class.json_parser_include_request?
115
+ parser.call(str, self)
116
+ else
117
+ parser.call(str)
118
+ end
105
119
  end
106
120
 
107
121
  # Rack 3 dropped requirement that input be rewindable
@@ -285,11 +285,14 @@ class Roda
285
285
  class UnhandledMail < StandardError; end
286
286
 
287
287
  module ClassMethods
288
+ RodaPlugins.opt_attr_reader(self, :mail_processor_string_routes)
289
+ RodaPlugins.opt_attr_reader(self, :mail_processor_regexp_routes)
290
+
288
291
  # Freeze the rcpt routes if they are present.
289
292
  def freeze
290
- if string_routes = opts[:mail_processor_string_routes].freeze
293
+ if string_routes = mail_processor_string_routes.freeze
291
294
  string_routes.freeze
292
- opts[:mail_processor_regexp_routes].freeze
295
+ mail_processor_regexp_routes.freeze
293
296
  end
294
297
  super
295
298
  end
@@ -337,12 +340,12 @@ class Roda
337
340
  unless string_meth
338
341
  string_meth = define_roda_method("mail_processor_string_route_#{address}", 1, &convert_route_block(block))
339
342
  end
340
- opts[:mail_processor_string_routes][address] = string_meth
343
+ mail_processor_string_routes[address] = string_meth
341
344
  when Regexp
342
345
  unless regexp_meth
343
346
  regexp_meth = define_roda_method("mail_processor_regexp_route_#{address}", :any, &convert_route_block(block))
344
347
  end
345
- opts[:mail_processor_regexp_routes][address] = regexp_meth
348
+ mail_processor_regexp_routes[address] = regexp_meth
346
349
  else
347
350
  raise RodaError, "invalid address format passed to rcpt, should be Array or String"
348
351
  end
@@ -382,7 +385,7 @@ class Roda
382
385
  # routes defined via the class-level +rcpt+ method, and then the
383
386
  # normal routing tree passed in as the block.
384
387
  def process_mail(&block)
385
- if string_routes = opts[:mail_processor_string_routes]
388
+ if string_routes = self.class.mail_processor_string_routes
386
389
  addresses = mail_recipients
387
390
 
388
391
  addresses.each do |address|
@@ -392,7 +395,7 @@ class Roda
392
395
  end
393
396
  end
394
397
 
395
- opts[:mail_processor_regexp_routes].each do |regexp, meth|
398
+ self.class.mail_processor_regexp_routes.each do |regexp, meth|
396
399
  addresses.each do |address|
397
400
  if md = regexp.match(address)
398
401
  _roda_handle_route{send(meth, @_request, *md.captures)}
@@ -136,10 +136,12 @@ class Roda
136
136
  # Set the options for the mailer. Options:
137
137
  # :content_type :: The default content type for emails (default: text/plain)
138
138
  def self.configure(app, opts=OPTS)
139
- app.opts[:mailer] = (app.opts[:mailer]||OPTS).merge(opts).freeze
139
+ app.opts[:mailer] = (app.mailer_opts||OPTS).merge(opts).freeze
140
140
  end
141
141
 
142
142
  module ClassMethods
143
+ RodaPlugins.opt_attr_reader(self, :mailer, name: :mailer_opts)
144
+
143
145
  # Return a Mail::Message instance for the email for the given request path
144
146
  # and arguments. Any arguments given are yielded to the appropriate +r.mail+
145
147
  # block after any usual match block arguments. You can further manipulate the
@@ -177,7 +179,7 @@ class Roda
177
179
  def mail(*args)
178
180
  if @env["REQUEST_METHOD"] == "MAIL"
179
181
  # RODA4: Make terminal match the default
180
- send(roda_class.opts[:mailer][:terminal] ? :_verb : :if_match, args) do |*vs|
182
+ send(roda_class.mailer_opts[:terminal] ? :_verb : :if_match, args) do |*vs|
181
183
  yield(*(vs + @env['roda.mail_args']))
182
184
  end
183
185
  end
@@ -202,7 +204,7 @@ class Roda
202
204
  block.call if block
203
205
  end
204
206
 
205
- if content_type = header_content_type || roda_class.opts[:mailer][:content_type]
207
+ if content_type = header_content_type || roda_class.mailer_opts[:content_type]
206
208
  if mail.multipart?
207
209
  if /multipart\/mixed/ =~ mail.content_type &&
208
210
  mail.parts.length >= 2 &&
@@ -39,13 +39,18 @@ class Roda
39
39
  app.opts[:match_suffix] = suffix if suffix
40
40
  end
41
41
 
42
+ module ClassMethods
43
+ RodaPlugins.opt_attr_reader(self, :match_prefix)
44
+ RodaPlugins.opt_attr_reader(self, :match_suffix)
45
+ end
46
+
42
47
  module RequestClassMethods
43
48
  private
44
49
 
45
50
  # Use the match prefix and suffix provided when loading the plugin, or fallback
46
51
  # to Roda's default prefix/suffix if one was not provided.
47
52
  def consume_pattern(pattern)
48
- /\A#{roda_class.opts[:match_prefix] || "/"}(?:#{pattern})#{roda_class.opts[:match_suffix] || "(?=\/|\z)"}/
53
+ /\A#{roda_class.match_prefix || "/"}(?:#{pattern})#{roda_class.match_suffix || "(?=\/|\z)"}/
49
54
  end
50
55
  end
51
56
 
@@ -40,20 +40,22 @@ class Roda
40
40
  end
41
41
 
42
42
  module ClassMethods
43
+ RodaPlugins.opt_attr_reader(self, :match_hook_args)
44
+
43
45
  # Freeze the array of hook methods when freezing the app
44
46
  def freeze
45
- opts[:match_hook_args].freeze
47
+ match_hook_args.freeze
46
48
  super
47
49
  end
48
50
 
49
51
  # Add a match hook that will be called with matchers and block args.
50
52
  def add_match_hook(&block)
51
- opts[:match_hook_args] << define_roda_method("match_hook_args", :any, &block)
53
+ match_hook_args << define_roda_method("match_hook_args", :any, &block)
52
54
 
53
- if opts[:match_hook_args].length == 1
54
- class_eval("alias _match_hook_args #{opts[:match_hook_args].first}", __FILE__, __LINE__)
55
+ if match_hook_args.length == 1
56
+ class_eval("alias _match_hook_args #{match_hook_args.first}", __FILE__, __LINE__)
55
57
  else
56
- class_eval("def _match_hook_args(v, a); #{opts[:match_hook_args].map{|m| "#{m}(v, a)"}.join(';')} end", __FILE__, __LINE__)
58
+ class_eval("def _match_hook_args(v, a); #{match_hook_args.map{|m| "#{m}(v, a)"}.join(';')} end", __FILE__, __LINE__)
57
59
  end
58
60
 
59
61
  public :_match_hook_args
@@ -136,10 +136,10 @@ class Roda
136
136
  def initialize(mid, app, *args, &block)
137
137
  @mid = Class.new(mid)
138
138
  RodaPlugins.set_temp_name(@mid){"#{mid}::middleware_subclass"}
139
- if @mid.opts[:middleware_next_if_not_found]
139
+ if @mid.middleware_next_if_not_found?
140
140
  @mid.plugin(:not_found, &NEXT_PROC)
141
141
  end
142
- if configure = @mid.opts[:middleware_configure]
142
+ if configure = @mid.middleware_configure
143
143
  configure.call(@mid, *args, &block)
144
144
  elsif block || !args.empty?
145
145
  raise RodaError, "cannot provide middleware args or block unless loading middleware plugin with a block"
@@ -154,7 +154,7 @@ class Roda
154
154
  res = nil
155
155
 
156
156
  call_next = catch(:next) do
157
- env[@mid.opts[:middleware_env_var]] = true
157
+ env[@mid.middleware_env_var] = true
158
158
  res = @mid.call(env)
159
159
  false
160
160
  end
@@ -167,7 +167,7 @@ class Roda
167
167
  end
168
168
  end
169
169
 
170
- if handle_result = @mid.opts[:middleware_handle_result]
170
+ if handle_result = @mid.middleware_handle_result
171
171
  handle_result.call(env, res)
172
172
  end
173
173
 
@@ -176,6 +176,12 @@ class Roda
176
176
  end
177
177
 
178
178
  module ClassMethods
179
+ RodaPlugins.opt_attr_reader(self, :middleware_env_var)
180
+ RodaPlugins.opt_attr_reader(self, :middleware_configure)
181
+ RodaPlugins.opt_attr_reader(self, :middleware_handle_result)
182
+ RodaPlugins.opt_attr_reader(self, :middleware_forward_response_headers)
183
+ RodaPlugins.opt_attr_reader(self, :middleware_next_if_not_found, name: :middleware_next_if_not_found?)
184
+
179
185
  # Create a Forwarder instead of a new instance if a non-Hash is given.
180
186
  def new(app, *args, &block)
181
187
  if app.is_a?(Hash)
@@ -193,7 +199,7 @@ class Roda
193
199
  super do |r|
194
200
  res = instance_exec(r, &block) # call Fallback
195
201
  if r.forward_next
196
- r.env['roda.response_headers'] = response.headers if opts[:middleware_forward_response_headers]
202
+ r.env['roda.response_headers'] = response.headers if self.class.middleware_forward_response_headers
197
203
  throw :next, true
198
204
  end
199
205
  res
@@ -205,7 +211,7 @@ class Roda
205
211
  def _roda_run_main_route(r)
206
212
  res = super
207
213
  if r.forward_next
208
- r.env['roda.response_headers'] = response.headers if opts[:middleware_forward_response_headers]
214
+ r.env['roda.response_headers'] = response.headers if self.class.middleware_forward_response_headers
209
215
  throw :next, true
210
216
  end
211
217
  res
@@ -216,7 +222,7 @@ class Roda
216
222
  # Whether to forward the request to the next application. Set only if
217
223
  # this request is being performed for middleware.
218
224
  def forward_next
219
- env[roda_class.opts[:middleware_env_var]]
225
+ env[roda_class.middleware_env_var]
220
226
  end
221
227
  end
222
228
  end
@@ -83,7 +83,7 @@ class Roda
83
83
  klass.send(:include, mod)
84
84
  end
85
85
 
86
- mod.module_eval(&block) if block
86
+ mod.module_eval(&block) if block # RODA4: Switch to module_exec
87
87
  end
88
88
 
89
89
  mod
@@ -72,7 +72,7 @@ class Roda
72
72
 
73
73
  # Use the given directories to setup servers. Any opts are passed to the public plugin.
74
74
  def self.configure(app, directories, _=OPTS)
75
- roots = app.opts[:multi_public_servers] = (app.opts[:multi_public_servers] || {}).dup
75
+ roots = app.opts[:multi_public_servers] = (app.multi_public_servers || {}).dup
76
76
  directories.each do |key, path|
77
77
  path, headers, mime = path
78
78
  roots[key] = RACK_FILES.new(app.expand_path(path), headers||{}, mime||'text/plain')
@@ -80,11 +80,15 @@ class Roda
80
80
  roots.freeze
81
81
  end
82
82
 
83
+ module ClassMethods
84
+ RodaPlugins.opt_attr_reader(self, :multi_public_servers)
85
+ end
86
+
83
87
  module RequestMethods
84
88
  # Serve files from the directory corresponding to the given key if the file exists and
85
89
  # this is a GET request.
86
90
  def multi_public(key)
87
- public_serve_with(roda_class.opts[:multi_public_servers].fetch(key))
91
+ public_serve_with(roda_class.multi_public_servers.fetch(key))
88
92
  end
89
93
  end
90
94
  end
@@ -94,7 +94,7 @@ class Roda
94
94
  # Freeze the multi_route regexp matchers so that there can be no thread safety issues at runtime.
95
95
  def freeze
96
96
  super
97
- opts[:namespaced_routes].each_key do |k|
97
+ namespaced_routes.each_key do |k|
98
98
  self::RodaRequest.named_route_regexp(k)
99
99
  end
100
100
  self::RodaRequest.instance_variable_get(:@namespaced_route_regexps).freeze
@@ -70,12 +70,15 @@ class Roda
70
70
  end
71
71
 
72
72
  module ClassMethods
73
+ RodaPlugins.opt_attr_reader(self, :multi_run_apps)
74
+ RodaPlugins.opt_attr_reader(self, :multi_run_app_blocks)
75
+
73
76
  # Convert app blocks into apps by calling them, in order to force autoloads
74
77
  # and to speed up subsequent calls.
75
78
  # Freeze the multi_run apps so that there can be no thread safety issues at runtime.
76
79
  def freeze
77
- app_blocks = opts[:multi_run_app_blocks]
78
- apps = opts[:multi_run_apps]
80
+ app_blocks = multi_run_app_blocks
81
+ apps = multi_run_apps
79
82
  app_blocks.each do |prefix, block|
80
83
  apps[prefix] = block.call
81
84
  end
@@ -85,12 +88,6 @@ class Roda
85
88
  super
86
89
  end
87
90
 
88
- # Hash storing rack applications to dispatch to, keyed by the prefix
89
- # for the application.
90
- def multi_run_apps
91
- opts[:multi_run_apps]
92
- end
93
-
94
91
  # Add a rack application to dispatch to for the given prefix when
95
92
  # r.multi_run is called. If a block is given, it is called every time
96
93
  # there is a request for the route to get the app to call. If neither
@@ -100,14 +97,14 @@ class Roda
100
97
  prefix = prefix.to_s
101
98
  if app
102
99
  raise Roda::RodaError, "cannot provide both app and block to Roda.run" if block
103
- opts[:multi_run_apps][prefix] = app
104
- opts[:multi_run_app_blocks].delete(prefix)
100
+ multi_run_apps[prefix] = app
101
+ multi_run_app_blocks.delete(prefix)
105
102
  elsif block
106
- opts[:multi_run_apps].delete(prefix)
107
- opts[:multi_run_app_blocks][prefix] = block
103
+ multi_run_apps.delete(prefix)
104
+ multi_run_app_blocks[prefix] = block
108
105
  else
109
- opts[:multi_run_apps].delete(prefix)
110
- opts[:multi_run_app_blocks].delete(prefix)
106
+ multi_run_apps.delete(prefix)
107
+ multi_run_app_blocks.delete(prefix)
111
108
  end
112
109
  self::RodaRequest.refresh_multi_run_regexp!
113
110
  end
@@ -117,7 +114,7 @@ class Roda
117
114
  # Refresh the multi_run_regexp, using the stored route prefixes,
118
115
  # preferring longer routes before shorter routes.
119
116
  def refresh_multi_run_regexp!
120
- @multi_run_regexp = /(#{Regexp.union((roda_class.opts[:multi_run_apps].keys + roda_class.opts[:multi_run_app_blocks].keys).sort.reverse)})/
117
+ @multi_run_regexp = /(#{Regexp.union((roda_class.multi_run_apps.keys + roda_class.multi_run_app_blocks.keys).sort.reverse)})/
121
118
  end
122
119
 
123
120
  # Refresh the multi_run_regexp if it hasn't been loaded yet.
@@ -132,8 +129,7 @@ class Roda
132
129
  def multi_run
133
130
  on self.class.multi_run_regexp do |prefix|
134
131
  yield prefix if defined?(yield)
135
- opts = scope.opts
136
- run(opts[:multi_run_apps][prefix] || opts[:multi_run_app_blocks][prefix].call)
132
+ run(roda_class.multi_run_apps[prefix] || roda_class.multi_run_app_blocks[prefix].call)
137
133
  end
138
134
  end
139
135
  end
@@ -111,22 +111,24 @@ class Roda
111
111
  end
112
112
 
113
113
  module ClassMethods
114
+ RodaPlugins.opt_attr_reader(self, :namespaced_routes)
115
+
114
116
  # Freeze the namespaced routes so that there can be no thread safety issues at runtime.
115
117
  def freeze
116
- opts[:namespaced_routes].freeze.each_value(&:freeze)
118
+ namespaced_routes.freeze.each_value(&:freeze)
117
119
  super
118
120
  end
119
121
 
120
122
  # Copy the named routes into the subclass when inheriting.
121
123
  def inherited(subclass)
122
124
  super
123
- nsr = subclass.opts[:namespaced_routes]
124
- opts[:namespaced_routes].each{|k, v| nsr[k] = v.dup}
125
+ nsr = subclass.namespaced_routes
126
+ namespaced_routes.each{|k, v| nsr[k] = v.dup}
125
127
  end
126
128
 
127
129
  # The names for the currently stored named routes
128
130
  def named_routes(namespace=nil)
129
- unless routes = opts[:namespaced_routes][namespace]
131
+ unless routes = namespaced_routes[namespace]
130
132
  raise RodaError, "unsupported named_routes namespace used: #{namespace.inspect}"
131
133
  end
132
134
  routes.keys
@@ -134,7 +136,7 @@ class Roda
134
136
 
135
137
  # Return the named route with the given name.
136
138
  def named_route(name, namespace=nil)
137
- opts[:namespaced_routes][namespace][name]
139
+ namespaced_routes[namespace][name]
138
140
  end
139
141
 
140
142
  # If the given route has a name, treat it as a named route and
@@ -142,7 +144,7 @@ class Roda
142
144
  # call super.
143
145
  def route(name=nil, namespace=nil, &block)
144
146
  if name
145
- routes = opts[:namespaced_routes][namespace] ||= {}
147
+ routes = namespaced_routes[namespace] ||= {}
146
148
  if block
147
149
  routes[name] = define_roda_method(routes[name] || "named_routes_#{namespace}_#{name}", 1, &convert_route_block(block))
148
150
  elsif meth = routes.delete(name)
@@ -61,15 +61,17 @@ class Roda
61
61
  end
62
62
 
63
63
  module ClassMethods
64
+ RodaPlugins.opt_attr_reader(self, :named_templates)
65
+
64
66
  # Freeze the named templates so that there can be no thread safety issues at runtime.
65
67
  def freeze
66
- opts[:named_templates].freeze
68
+ named_templates.freeze
67
69
  super
68
70
  end
69
71
 
70
72
  # Store a new template block and options for the given template name.
71
73
  def template(name, options=nil, &block)
72
- opts[:named_templates][name.to_s] = [options, define_roda_method("named_templates_#{name}", 0, &block)].freeze
74
+ named_templates[name.to_s] = [options, define_roda_method("named_templates_#{name}", 0, &block)].freeze
73
75
  nil
74
76
  end
75
77
  end
@@ -80,7 +82,7 @@ class Roda
80
82
  # If a template name is given and it matches a named template, call
81
83
  # the named template block to get the inline template to use.
82
84
  def find_template(options)
83
- if options[:template] && (template_opts, meth = opts[:named_templates][template_name(options)]; meth)
85
+ if options[:template] && (template_opts, meth = self.class.named_templates[template_name(options)]; meth)
84
86
  if template_opts
85
87
  options = template_opts.merge(options)
86
88
  else
@@ -80,11 +80,15 @@ class Roda
80
80
  # Regexp for valid constant names, to prevent code execution.
81
81
  VALID_CONSTANT_NAME_REGEXP = /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/.freeze
82
82
 
83
+ def self.load_dependencies(app, opts = OPTS)
84
+ app.plugin :_add_script_name
85
+ end
86
+
83
87
  # Initialize the path classes when loading the plugin. Options:
84
88
  # :by_name :: Register classes by name, which is friendlier when reloading code (defaults to
85
89
  # true in development mode)
86
90
  def self.configure(app, opts=OPTS)
87
- app.instance_eval do
91
+ app.instance_exec do
88
92
  self.opts[:path_class_by_name] = opts.fetch(:by_name, ENV['RACK_ENV'] == 'development')
89
93
  self.opts[:path_classes] ||= {}
90
94
  self.opts[:path_class_methods] ||= {}
@@ -95,15 +99,14 @@ class Roda
95
99
  end
96
100
 
97
101
  module ClassMethods
98
- # Hash of recognizes classes for path instance method. Keys are classes, values are procs.
99
- def path_classes
100
- opts[:path_classes]
101
- end
102
+ RodaPlugins.opt_attr_reader(self, :path_classes)
103
+ RodaPlugins.opt_attr_reader(self, :path_class_by_name, name: :path_class_by_name?)
104
+ RodaPlugins.opt_attr_reader(self, :path_class_methods)
102
105
 
103
106
  # Freeze the path classes when freezing the app.
104
107
  def freeze
105
108
  path_classes.freeze
106
- opts[:path_classes_methods].freeze
109
+ path_class_methods.freeze
107
110
  super
108
111
  end
109
112
 
@@ -113,7 +116,7 @@ class Roda
113
116
  raise RodaError, "can't provide path when calling path with a class" if path && !class_name
114
117
  raise RodaError, "can't provide options when calling path with a class" unless opts.empty?
115
118
  raise RodaError, "must provide a block when calling path with a class" unless block
116
- if self.opts[:path_class_by_name]
119
+ if path_class_by_name?
117
120
  if class_name
118
121
  name = name.to_s
119
122
  else
@@ -125,7 +128,7 @@ class Roda
125
128
  name = Object.class_eval(name, __FILE__, __LINE__)
126
129
  end
127
130
  path_classes[name] = block
128
- self.opts[:path_class_methods][name] = define_roda_method("path_#{name}", :any, &block)
131
+ path_class_methods[name] = define_roda_method("path_#{name}", :any, &block)
129
132
  return
130
133
  end
131
134
 
@@ -147,7 +150,7 @@ class Roda
147
150
  url = opts[:url]
148
151
  url_only = opts[:url_only]
149
152
  relative = opts[:relative]
150
- add_script_name = opts.fetch(:add_script_name, self.opts[:add_script_name])
153
+ add_script_name = opts.fetch(:add_script_name){add_script_name?}
151
154
 
152
155
  if relative
153
156
  if (url || url_only)
@@ -209,7 +212,7 @@ class Roda
209
212
  # Return the block related to the given class, or nil if there is no block.
210
213
  def path_block(klass)
211
214
  # RODA4: Remove
212
- if opts[:path_class_by_name]
215
+ if path_class_by_name?
213
216
  klass = klass.name
214
217
  end
215
218
  path_classes[klass]
@@ -222,14 +225,13 @@ class Roda
222
225
  # :add_script_name option is true, this prepends the SCRIPT_NAME to the path.
223
226
  def path(obj, *args, &block)
224
227
  app = self.class
225
- opts = app.opts
226
- klass = opts[:path_class_by_name] ? obj.class.name : obj.class
227
- unless meth = opts[:path_class_methods][klass]
228
+ klass = app.path_class_by_name? ? obj.class.name : obj.class
229
+ unless meth = app.path_class_methods[klass]
228
230
  raise RodaError, "unrecognized object given to Roda#path: #{obj.inspect}"
229
231
  end
230
232
 
231
233
  path = send(meth, obj, *args, &block)
232
- path = request.script_name.to_s + path if opts[:add_script_name]
234
+ path = request.script_name.to_s + path if app.add_script_name?
233
235
  path
234
236
  end
235
237