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
@@ -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)}
@@ -510,11 +513,11 @@ class Roda
510
513
  a1.casecmp?(a2)
511
514
  end
512
515
  else
513
- # :nocov:
516
+ # simplecov:disable
514
517
  def address_match?(a1, a2)
515
518
  a1.downcase == a2.downcase
516
519
  end
517
- # :nocov:
520
+ # simplecov:enable
518
521
  end
519
522
 
520
523
  # Match if any of the given addresses match the given val, which
@@ -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
@@ -153,9 +155,9 @@ class Roda
153
155
  mail
154
156
  end
155
157
  end
156
- # :nocov:
158
+ # simplecov:disable
157
159
  ruby2_keywords(:mail) if respond_to?(:ruby2_keywords, true)
158
- # :nocov:
160
+ # simplecov:enable
159
161
 
160
162
  # Calls +mail+ with given arguments and immediately sends the resulting mail.
161
163
  def sendmail(*args)
@@ -163,9 +165,9 @@ class Roda
163
165
  m.deliver
164
166
  end
165
167
  end
166
- # :nocov:
168
+ # simplecov:disable
167
169
  ruby2_keywords(:sendmail) if respond_to?(:ruby2_keywords, true)
168
- # :nocov:
170
+ # simplecov:enable
169
171
  end
170
172
 
171
173
  module RequestMethods
@@ -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
@@ -27,11 +27,16 @@ class Roda
27
27
  rp = @remaining_path
28
28
  if rp.getbyte(0) == 47
29
29
  if last = rp.index('/', 1)
30
- @remaining_path = rp[last, rp.length]
31
- always{yield rp[1, last-1]}
30
+ return false if last == 1
31
+ always do
32
+ @remaining_path = rp[last, rp.length]
33
+ yield rp[1, last-1]
34
+ end
32
35
  elsif (len = rp.length) > 1
33
- @remaining_path = ""
34
- always{yield rp[1, len]}
36
+ always do
37
+ @remaining_path = ""
38
+ yield rp[1, len]
39
+ end
35
40
  end
36
41
  end
37
42
  end
@@ -41,8 +46,10 @@ class Roda
41
46
  def is_segment
42
47
  rp = @remaining_path
43
48
  if rp.getbyte(0) == 47 && !rp.index('/', 1) && (len = rp.length) > 1
44
- @remaining_path = ""
45
- always{yield rp[1, len]}
49
+ always do
50
+ @remaining_path = ""
51
+ yield rp[1, len]
52
+ end
46
53
  end
47
54
  end
48
55
  end
@@ -28,7 +28,11 @@ class Roda
28
28
  module RequestMethods
29
29
  # Optimized version of +on+ that only supports a single string.
30
30
  def on_branch(s)
31
- always{yield} if _match_string(s)
31
+ rp = @remaining_path
32
+ if _match_string(s)
33
+ always{yield}
34
+ @remaining_path = rp
35
+ end
32
36
  end
33
37
 
34
38
  # Optimized version of +is+ that only supports a single string.
@@ -37,9 +41,8 @@ class Roda
37
41
  if _match_string(s)
38
42
  if @remaining_path.empty?
39
43
  always{yield}
40
- else
41
- @remaining_path = rp
42
44
  end
45
+ @remaining_path = rp
43
46
  end
44
47
  end
45
48
  end
@@ -54,14 +54,20 @@ class Roda
54
54
  # Also note that this plugin will not work correctly if you are using
55
55
  # the symbol_matchers plugin with custom symbol matching and are using
56
56
  # symbols that capture multiple values or no values.
57
+ #
58
+ # If you are using the pass or break plugin along with this plugin,
59
+ # it is recommended you load the params_capturing_restore plugin, as that
60
+ # allows +r.pass+ or +break+ inside param capturing blocking to work
61
+ # correctly.
57
62
  module ParamsCapturing
58
63
  REQUEST_INSTANCE_VARIABLES = [:@_params_captures].freeze
59
64
 
60
65
  module RequestMethods
61
66
  # Lazily initialize captures entry when params is called.
62
67
  def params
68
+ return @params if @params
63
69
  ret = super
64
- ret['captures'] ||= []
70
+ ret['captures'] = []
65
71
  ret
66
72
  end
67
73
 
@@ -100,16 +106,19 @@ class Roda
100
106
  end
101
107
 
102
108
  super do |*a|
103
- if pc
104
- @_params_captures = nil
105
- pc.zip(a).each do |k,v|
106
- params[k] = v
107
- end
108
- end
109
+ _set_params_captures(a) if pc
109
110
  params['captures'].concat(a)
110
111
  yield(*a)
111
112
  end
112
113
  end
114
+
115
+ # Set the captures into the parameters.
116
+ def _set_params_captures(a)
117
+ @_params_captures.zip(a).each do |k,v|
118
+ params[k] = v
119
+ end
120
+ @_params_captures = nil
121
+ end
113
122
  end
114
123
  end
115
124
 
@@ -0,0 +1,72 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The params_capturing_restore plugin allows the params_capturing plugin
7
+ # to work with the use of +r.pass+ (pass plugin) or +break+ (break plugin)
8
+ # inside a block that uses param capturing, restoring the previous state of
9
+ # the params.
10
+ #
11
+ # This is not done by the params_capturing plugin as it requires significant
12
+ # extra work for every param capturing block, and most users of the
13
+ # params_capturing plugin are not using the pass or break plugins.
14
+ module ParamsCapturingRestore
15
+ REQUEST_INSTANCE_VARIABLES = [:@_params_captures_restore].freeze
16
+
17
+ ABSENT = Object.new.freeze
18
+ private_constant :ABSENT
19
+
20
+ def self.load_dependencies(app)
21
+ app.plugin :params_capturing
22
+ end
23
+
24
+ module RequestMethods
25
+ private
26
+
27
+ # Restore previous state of the params before capturing when
28
+ # the block exits.
29
+ def if_match(args)
30
+ params = self.params
31
+ num_captures = params['captures'].length
32
+ old_params = nil
33
+
34
+ super do |*a|
35
+ old_params = @_params_captures_restore
36
+ @_params_captures_restore = nil
37
+ yield(*a)
38
+ end
39
+ ensure
40
+ if old_params
41
+ old_params.each_slice(2) do |k, v|
42
+ if v == ABSENT
43
+ params.delete(k)
44
+ else
45
+ params[k] = v
46
+ end
47
+ end
48
+ end
49
+
50
+ captures = params['captures']
51
+ if num_captures != captures.length
52
+ captures.slice!(num_captures, 1000000)
53
+ end
54
+ end
55
+
56
+ # Record the original value of the params before calling super
57
+ # to update the params.
58
+ def _set_params_captures(a)
59
+ restore = @_params_captures_restore = []
60
+ @_params_captures.zip(a).each do |k,v|
61
+ restore << k << params.fetch(k, ABSENT)
62
+ end
63
+
64
+ super
65
+ end
66
+ end
67
+ end
68
+
69
+ register_plugin(:params_capturing_restore, ParamsCapturingRestore)
70
+ end
71
+ end
72
+
@@ -44,19 +44,19 @@ class Roda
44
44
  render(template, :locals=>locals, &block)
45
45
  end
46
46
  RUBY
47
- # :nocov:
47
+ # simplecov:disable
48
48
  else
49
49
  def part(template, locals=OPTS, &block)
50
50
  render(template, :locals=>locals, &block)
51
51
  end
52
52
  end
53
- # :nocov:
53
+ # simplecov:enable
54
54
  end
55
55
 
56
56
  module AssumeFixedLocalsInstanceMethods
57
- # :nocov:
57
+ # simplecov:disable
58
58
  if RUBY_VERSION >= '3.0'
59
- # :nocov:
59
+ # simplecov:enable
60
60
  class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
61
61
  def part(template, ...)
62
62
  if optimized_method = _optimized_render_method_for_locals(template, OPTS)
@@ -29,7 +29,10 @@ class Roda
29
29
 
30
30
  # Handle passing inside the match block.
31
31
  def always
32
- catch(:pass){super}
32
+ rp = @remaining_path
33
+ ret = catch(:pass){super}
34
+ @remaining_path = rp
35
+ ret
33
36
  end
34
37
 
35
38
  # Handle passing inside the match block.