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
@@ -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)
@@ -169,17 +172,17 @@ class Roda
169
172
  # Allow calling private _method to get path
170
173
  relative_path(request.script_name.to_s + send(_meth, *a, &blk))
171
174
  end
172
- # :nocov:
175
+ # simplecov:disable
173
176
  ruby2_keywords(meth) if respond_to?(:ruby2_keywords, true)
174
- # :nocov:
177
+ # simplecov:enable
175
178
  elsif add_script_name
176
179
  define_method(meth) do |*a, &blk|
177
180
  # Allow calling private _method to get path
178
181
  request.script_name.to_s + send(_meth, *a, &blk)
179
182
  end
180
- # :nocov:
183
+ # simplecov:disable
181
184
  ruby2_keywords(meth) if respond_to?(:ruby2_keywords, true)
182
- # :nocov:
185
+ # simplecov:enable
183
186
  else
184
187
  define_method(meth, &block)
185
188
  end
@@ -198,9 +201,9 @@ class Roda
198
201
  end
199
202
 
200
203
  define_method(url_meth, &url_block)
201
- # :nocov:
204
+ # simplecov:disable
202
205
  ruby2_keywords(url_meth) if respond_to?(:ruby2_keywords, true)
203
- # :nocov:
206
+ # simplecov:enable
204
207
  end
205
208
 
206
209
  nil
@@ -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
 
@@ -29,12 +29,16 @@ class Roda
29
29
  # # Matches '/foo.bar', yielding 'foo'
30
30
  # # Does not match bar.foo
31
31
  # end
32
+ #
33
+ # Note that all of these matchers treat a given string as regexp source,
34
+ # so if you are including regexp metacharacters but want a literal string
35
+ # match, use +Regexp.escape+.
32
36
  module PathMatchers
33
37
  module RequestMethods
34
38
  # Match when the current segment ends with the given extension.
35
39
  # request path end with the extension.
36
40
  def match_extension(ext)
37
- match_suffix(".#{ext}")
41
+ match_suffix("\\.#{ext}")
38
42
  end
39
43
 
40
44
  # Match when the current path segment starts with the given prefix.
@@ -53,10 +53,13 @@ class Roda
53
53
  end
54
54
 
55
55
  module ClassMethods
56
+ RodaPlugins.opt_attr_reader(self, :remaining_path_rewrites)
57
+ RodaPlugins.opt_attr_reader(self, :path_info_rewrites)
58
+
56
59
  # Freeze the path rewrite metadata.
57
60
  def freeze
58
- opts[:remaining_path_rewrites].freeze
59
- opts[:path_info_rewrites].freeze
61
+ remaining_path_rewrites.freeze
62
+ path_info_rewrites.freeze
60
63
  super
61
64
  end
62
65
 
@@ -75,7 +78,7 @@ class Roda
75
78
  end
76
79
 
77
80
  was = /\A#{Regexp.escape(was)}/ unless was.is_a?(Regexp)
78
- array = @opts[opts[:path_info] ? :path_info_rewrites : :remaining_path_rewrites]
81
+ array = opts[:path_info] ? path_info_rewrites : remaining_path_rewrites
79
82
  array << [was, is.dup.freeze].freeze
80
83
  end
81
84
  end
@@ -85,10 +88,10 @@ class Roda
85
88
  def initialize(scope, env)
86
89
  path_info = env['PATH_INFO']
87
90
 
88
- rewrite_path(scope.class.opts[:path_info_rewrites], path_info)
91
+ rewrite_path(scope.class.path_info_rewrites, path_info)
89
92
  super
90
93
  remaining_path = @remaining_path = @remaining_path.dup
91
- rewrite_path(scope.class.opts[:remaining_path_rewrites], remaining_path)
94
+ rewrite_path(scope.class.remaining_path_rewrites, remaining_path)
92
95
  end
93
96
 
94
97
  private
@@ -280,7 +280,7 @@ class Roda
280
280
 
281
281
  # Yield the current Permissions Policy to the block.
282
282
  def self.configure(app, opts=OPTS)
283
- policy = app.opts[:permissions_policy] = if policy = app.opts[:permissions_policy]
283
+ policy = app.opts[:permissions_policy] = if policy = app.permissions_policy
284
284
  policy.dup
285
285
  else
286
286
  Policy.new
@@ -296,6 +296,10 @@ class Roda
296
296
  policy.freeze
297
297
  end
298
298
 
299
+ module ClassMethods
300
+ RodaPlugins.opt_attr_reader(self, :permissions_policy)
301
+ end
302
+
299
303
  module InstanceMethods
300
304
  # If a block is given, yield the current permission policy. Returns the
301
305
  # current permissions policy.
@@ -315,7 +319,7 @@ class Roda
315
319
 
316
320
  # The current permissions policy to be used for this response.
317
321
  def permissions_policy
318
- @permissions_policy ||= roda_class.opts[:permissions_policy].dup
322
+ @permissions_policy ||= roda_class.permissions_policy.dup
319
323
  end
320
324
 
321
325
  # Do not set a permissions policy header for this response.
@@ -329,7 +333,7 @@ class Roda
329
333
  def set_default_headers
330
334
  super
331
335
  unless @skip_permissions_policy
332
- (@permissions_policy || roda_class.opts[:permissions_policy]).set_header(headers)
336
+ (@permissions_policy || roda_class.permissions_policy).set_header(headers)
333
337
  end
334
338
  end
335
339
  end
@@ -46,10 +46,10 @@ class Roda
46
46
  ENCODING_MAP = {:zstd=>'zstd', :brotli=>'br', :gzip=>'gzip'}.freeze
47
47
  ENCODING_EXTENSIONS = {'br'=>'.br', 'gzip'=>'.gz', 'zstd'=>'.zst'}.freeze
48
48
 
49
- # :nocov:
49
+ # simplecov:disable
50
50
  PARSER = defined?(::URI::RFC2396_PARSER) ? ::URI::RFC2396_PARSER : ::URI::DEFAULT_PARSER
51
51
  MATCH_METHOD = RUBY_VERSION >= '2.4' ? :match? : :match
52
- # :nocov:
52
+ # simplecov:enable
53
53
 
54
54
  # Use options given to setup a Rack::File instance for serving files. Options:
55
55
  # :brotli :: Whether to serve already brotli-compressed files with a .br extension
@@ -69,19 +69,15 @@ class Roda
69
69
  # :zstd :: Whether to serve already zstd-compressed files with a .zst extension
70
70
  # for clients supporting "zstd" transfer encoding.
71
71
  def self.configure(app, opts={})
72
- if opts[:root]
73
- app.opts[:public_root] = app.expand_path(opts[:root])
74
- elsif !app.opts[:public_root]
75
- app.opts[:public_root] = app.expand_path("public")
76
- end
77
- app.opts[:public_server] = RACK_FILES.new(app.opts[:public_root], opts[:headers]||{}, opts[:default_mime] || 'text/plain')
72
+ app.opts[:public_root] = app.expand_path(opts[:root] || app.public_root || "public")
73
+ app.opts[:public_server] = RACK_FILES.new(app.public_root, opts[:headers]||{}, opts[:default_mime] || 'text/plain')
78
74
 
79
75
  unless encodings = opts[:encodings]
80
76
  if ENCODING_MAP.any?{|k,| opts.has_key?(k)}
81
77
  encodings = ENCODING_MAP.map{|k, v| [v, ENCODING_EXTENSIONS[v]] if opts[k]}.compact
82
78
  end
83
79
  end
84
- encodings = (encodings || app.opts[:public_encodings] || EMPTY_ARRAY).map(&:dup).freeze
80
+ encodings = (encodings || app.public_encodings || EMPTY_ARRAY).map(&:dup).freeze
85
81
  encodings.each do |a|
86
82
  a << /\b#{a[0]}\b/
87
83
  end
@@ -89,10 +85,16 @@ class Roda
89
85
  app.opts[:public_encodings] = encodings
90
86
  end
91
87
 
88
+ module ClassMethods
89
+ RodaPlugins.opt_attr_reader(self, :public_root)
90
+ RodaPlugins.opt_attr_reader(self, :public_server)
91
+ RodaPlugins.opt_attr_reader(self, :public_encodings)
92
+ end
93
+
92
94
  module RequestMethods
93
95
  # Serve files from the public directory if the file exists and this is a GET request.
94
96
  def public
95
- public_serve_with(roda_class.opts[:public_server])
97
+ public_serve_with(roda_class.public_server)
96
98
  end
97
99
 
98
100
  private
@@ -114,9 +116,9 @@ class Roda
114
116
  def public_file_readable?(path)
115
117
  ::File.file?(path) && ::File.readable?(path)
116
118
  rescue SystemCallError
117
- # :nocov:
119
+ # simplecov:disable
118
120
  false
119
- # :nocov:
121
+ # simplecov:enable
120
122
  end
121
123
 
122
124
  def public_serve_with(server)
@@ -124,11 +126,10 @@ class Roda
124
126
  path = PARSER.unescape(real_remaining_path)
125
127
  return if path.include?("\0")
126
128
 
127
- roda_opts = roda_class.opts
128
129
  path = ::File.join(server.root, *public_path_segments(path))
129
130
 
130
131
  if accept_encoding = env['HTTP_ACCEPT_ENCODING']
131
- roda_opts[:public_encodings].each do |enc, ext, regexp|
132
+ roda_class.public_encodings.each do |enc, ext, regexp|
132
133
  if regexp.send(MATCH_METHOD, accept_encoding)
133
134
  public_serve_compressed(server, path, ext, enc)
134
135
  end
@@ -57,7 +57,7 @@ class Roda
57
57
  precompiled_file = app.assets_opts[:precompiled]
58
58
  prev_mtime = ::File.mtime(precompiled_file)
59
59
  app.instance_exec do
60
- opts[:assets] = opts[:assets].merge(:compiled=>_compiled_assets_initial_hash.replace(assets_opts[:compiled])).freeze
60
+ opts[:assets] = assets_opts.merge(:compiled=>_compiled_assets_initial_hash.replace(assets_opts[:compiled])).freeze
61
61
 
62
62
  define_singleton_method(:recheck_precompiled_assets) do
63
63
  new_mtime = ::File.mtime(precompiled_file)
@@ -53,13 +53,16 @@ class Roda
53
53
  # uses a hash that redirects GET and HEAD requests with a 301 status,
54
54
  # and other request methods with a 307 status.
55
55
  def self.configure(app, opts=OPTS)
56
- previous = app.opts[:redirect_http_to_https] || DEFAULTS
57
- opts = app.opts[:redirect_http_to_https] = previous.merge(opts)
56
+ opts = app.opts[:redirect_http_to_https] = (app.redirect_http_to_https_opts || DEFAULTS).merge(opts)
58
57
  opts[:port_string] = opts[:port] ? ":#{opts[:port]}".freeze : "".freeze
59
58
  opts[:prefix] = opts[:host] ? "https://#{opts[:host]}#{opts[:port_string]}".freeze : nil
60
59
  opts.freeze
61
60
  end
62
61
 
62
+ module ClassMethods
63
+ RodaPlugins.opt_attr_reader(self, :redirect_http_to_https, name: :redirect_http_to_https_opts)
64
+ end
65
+
63
66
  module RequestMethods
64
67
  # Redirect HTTP requests to HTTPS. While this doesn't secure the
65
68
  # current request, it makes it more likely that the browser will submit
@@ -67,7 +70,7 @@ class Roda
67
70
  def redirect_http_to_https
68
71
  return if ssl?
69
72
 
70
- opts = roda_class.opts[:redirect_http_to_https]
73
+ opts = roda_class.redirect_http_to_https_opts
71
74
 
72
75
  res = response
73
76
 
@@ -46,8 +46,11 @@ class Roda
46
46
  #
47
47
  # The following plugin options are supported:
48
48
  #
49
- # :allowed_paths :: Set the template paths to allow. Attempts to render paths outside
50
- # of these paths will raise an error. Defaults to the +:views+ directory.
49
+ # :allowed_paths :: Set the template directories to allow. Attempts to render paths outside
50
+ # of these directories will raise an error. Defaults to the +:views+ option,
51
+ # if it is a directory. For backwards compatibility, if a non-directory is
52
+ # given, it will be used as a prefix match. Roda 4 will raise if a non-directory
53
+ # is given as an allowed path and +:check_paths+ is true.
51
54
  # :assume_fixed_locals :: Set if you are sure all templates in your application use fixed locals
52
55
  # to allow for additional optimization. This is ignored unless both
53
56
  # compiled methods and fixed locals are not supported.
@@ -298,42 +301,51 @@ class Roda
298
301
  def self.tilt_template_fixed_locals?(template)
299
302
  template.fixed_locals?
300
303
  end
301
- # :nocov:
304
+ # simplecov:disable
302
305
  else
303
306
  def self.tilt_template_fixed_locals?(template)
304
307
  false
305
308
  end
306
309
  end
307
- # :nocov:
310
+ # simplecov:enable
308
311
 
309
312
  if compiled_method_arity == -2
310
313
  def self.tilt_template_compiled_method(template, locals_keys, scope_class)
311
314
  template.send(:compiled_method, locals_keys, scope_class)
312
315
  end
313
- # :nocov:
316
+ # simplecov:disable
314
317
  else
315
318
  def self.tilt_template_compiled_method(template, locals_keys, scope_class)
316
319
  template.send(:compiled_method, locals_keys)
317
320
  end
318
- # :nocov:
321
+ # simplecov:enable
319
322
  end
320
323
 
321
324
  # Setup default rendering options. See Render for details.
322
325
  def self.configure(app, opts=OPTS)
323
- if app.opts[:render]
324
- orig_cache = app.opts[:render][:cache]
325
- orig_method_cache = app.opts[:render][:template_method_cache]
326
- opts = app.opts[:render][:orig_opts].merge(opts)
326
+ if app.render_opts
327
+ orig_cache = app.render_opts[:cache]
328
+ orig_method_cache = app.render_opts[:template_method_cache]
329
+ opts = app.render_opts[:orig_opts].merge(opts)
327
330
  end
328
331
  app.opts[:render] = opts.dup
329
- app.opts[:render][:orig_opts] = opts
332
+ app.render_opts[:orig_opts] = opts
330
333
 
331
- opts = app.opts[:render]
334
+ opts = app.render_opts
332
335
  opts[:engine] = (opts[:engine] || "erb").dup.freeze
333
336
  opts[:views] = app.expand_path(opts[:views]||"views").freeze
334
- opts[:allowed_paths] ||= [opts[:views]].freeze
335
- opts[:allowed_paths] = opts[:allowed_paths].map{|f| app.expand_path(f, nil)}.uniq.freeze
336
337
  opts[:check_paths] = true unless opts.has_key?(:check_paths)
338
+ opts[:allowed_paths] ||= [opts[:views]]
339
+ opts[:allowed_paths] = opts[:allowed_paths].map do |f|
340
+ path = app.expand_path(f, nil)
341
+ if File.directory?(path) || f == opts[:views]
342
+ path << "/"
343
+ elsif opts[:check_paths]
344
+ # RODA4: raise
345
+ RodaPlugins.warn("allowed path #{f.inspect} is not a directory in the file system. For backwards compatibility, will allow prefix matches, but this could result in security issues. Roda 4 will raise if an allowed path is not a directory.")
346
+ end
347
+ path.freeze
348
+ end.uniq.freeze
337
349
  opts[:assume_fixed_locals] &&= FIXED_LOCALS_COMPILED_METHOD_SUPPORT
338
350
 
339
351
  unless opts.has_key?(:check_template_mtime)
@@ -568,7 +580,7 @@ class Roda
568
580
  # affecting the parent class.
569
581
  def inherited(subclass)
570
582
  super
571
- opts = subclass.opts[:render] = subclass.opts[:render].dup
583
+ opts = subclass.opts[:render] = subclass.render_opts.dup
572
584
  if COMPILED_METHOD_SUPPORT
573
585
  opts[:template_method_cache] = (opts[:cache_class] || RodaCache).new
574
586
  end
@@ -576,10 +588,7 @@ class Roda
576
588
  opts.freeze
577
589
  end
578
590
 
579
- # Return the render options for this class.
580
- def render_opts
581
- opts[:render]
582
- end
591
+ RodaPlugins.opt_attr_reader(self, :render, name: :render_opts)
583
592
 
584
593
  private
585
594
 
@@ -591,13 +600,13 @@ class Roda
591
600
  # in order for the precompile_templates plugin to work correctly.
592
601
  instance.send(:retrieve_template, instance.send(:view_layout_opts, OPTS))
593
602
 
594
- if COMPILED_METHOD_SUPPORT && (layout_template = render_opts[:optimize_layout]) && !opts[:render][:optimized_layout_method_created]
603
+ if COMPILED_METHOD_SUPPORT && (layout_template = render_opts[:optimize_layout]) && !render_opts[:optimized_layout_method_created]
595
604
  instance.send(:retrieve_template, :template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout)
596
- layout_method = opts[:render][:template_method_cache][:_roda_layout]
605
+ layout_method = render_opts[:template_method_cache][:_roda_layout]
597
606
  define_method(:_layout_method){layout_method}
598
607
  private :_layout_method
599
608
  alias_method(:_layout_method, :_layout_method)
600
- opts[:render] = opts[:render].merge(:optimized_layout_method_created=>true)
609
+ opts[:render] = render_opts.merge(:optimized_layout_method_created=>true)
601
610
  end
602
611
  end
603
612
  end
@@ -638,7 +647,7 @@ class Roda
638
647
  # If we have an optimized template method but no optimized layout method, create the
639
648
  # optimized layout method if possible and use it. If you can't create the optimized
640
649
  # layout method, fall through to the slower approach.
641
- if layout_template = self.class.opts[:render][:optimize_layout]
650
+ if layout_template = self.class.render_opts[:optimize_layout]
642
651
  retrieve_template(:template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout)
643
652
  if layout_method = _layout_method
644
653
  return _call_optimized_template_method(layout_method, OPTS){content}
@@ -683,7 +692,7 @@ class Roda
683
692
 
684
693
  # Return a symbol containing the optimized layout method
685
694
  def _layout_method
686
- self.class.opts[:render][:template_method_cache][:_roda_layout]
695
+ self.class.render_opts[:template_method_cache][:_roda_layout]
687
696
  end
688
697
 
689
698
  # Use an optimized render path for templates with a hash of locals. Returns the result
@@ -754,40 +763,28 @@ class Roda
754
763
  end
755
764
 
756
765
  if RUBY_VERSION >= '3'
757
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
758
- def _call_optimized_template_method((meth, fixed_locals), locals, &block)
759
- if fixed_locals
760
- send(meth, **locals, &block)
761
- else
762
- send(meth, locals, &block)
763
- end
766
+ def _call_optimized_template_method((meth, fixed_locals), locals, &block)
767
+ if fixed_locals
768
+ send(meth, **locals, &block)
769
+ else
770
+ send(meth, locals, &block)
764
771
  end
765
- RUBY
766
- # :nocov:
767
- elsif RUBY_VERSION >= '2'
768
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
769
- def _call_optimized_template_method((meth, fixed_locals), locals, &block)
770
- if fixed_locals
771
- if locals.empty?
772
- send(meth, &block)
773
- else
774
- send(meth, **locals, &block)
775
- end
772
+ end
773
+ # simplecov:disable
774
+ else
775
+ def _call_optimized_template_method((meth, fixed_locals), locals, &block)
776
+ if fixed_locals
777
+ if locals.empty?
778
+ send(meth, &block)
776
779
  else
777
- send(meth, locals, &block)
780
+ send(meth, **locals, &block)
778
781
  end
782
+ else
783
+ send(meth, locals, &block)
779
784
  end
780
- RUBY
781
- else
782
- # Call the optimized template method. This is designed to be used with the
783
- # method cache, which caches the method name and whether the method uses
784
- # fixed locals. Methods with fixed locals need to be called with a keyword
785
- # splat.
786
- def _call_optimized_template_method((meth, fixed_locals), locals, &block)
787
- send(meth, locals, &block)
788
785
  end
789
786
  end
790
- # :nocov:
787
+ # simplecov:enable
791
788
  else
792
789
  def _cached_template_method(_)
793
790
  nil
@@ -832,7 +829,7 @@ class Roda
832
829
  # Given the template name and options, set the template class, template path/content,
833
830
  # template block, and locals to use for the render in the passed options.
834
831
  def find_template(opts)
835
- render_opts = self.class.opts[:render]
832
+ render_opts = self.class.render_opts
836
833
  engine_override = opts[:engine]
837
834
  engine = opts[:engine] ||= render_opts[:engine]
838
835
  if content = opts[:inline]
@@ -896,7 +893,7 @@ class Roda
896
893
  end
897
894
  cached_template(opts) do
898
895
  opts = found_template_opts || find_template(opts)
899
- render_opts = self.class.opts[:render]
896
+ render_opts = self.class.render_opts
900
897
  template_opts = render_opts[:template_opts]
901
898
  if engine_opts = render_opts[:engine_opts][opts[:engine]]
902
899
  template_opts = template_opts.merge(engine_opts)
@@ -981,14 +978,14 @@ class Roda
981
978
  end
982
979
 
983
980
  module AssumeFixedLocalsInstanceMethods
984
- # :nocov:
981
+ private
982
+
983
+ # simplecov:disable
985
984
  if RUBY_VERSION >= '3.0'
986
- # :nocov:
987
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
988
- def _call_optimized_template_method((meth,_), locals, &block)
989
- send(meth, **locals, &block)
990
- end
991
- RUBY
985
+ # simplecov:enable
986
+ def _call_optimized_template_method((meth,_), locals, &block)
987
+ send(meth, **locals, &block)
988
+ end
992
989
  end
993
990
  end
994
991
  end
@@ -1,9 +1,9 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  require 'tilt'
4
- # :nocov:
4
+ # simplecov:disable
5
5
  raise 'Tilt version does not support coverable templates' unless Tilt::Template.method_defined?(:compiled_path=)
6
- # :nocov:
6
+ # simplecov:enable
7
7
 
8
8
  #
9
9
  class Roda
@@ -42,11 +42,14 @@ class Roda
42
42
  # strip.
43
43
  def self.configure(app, opts=OPTS)
44
44
  app.opts[:render_coverage_strip_paths] = opts[:strip_paths].map{|f| File.expand_path(f)} if opts.has_key?(:strip_paths)
45
- coverage_dir = app.opts[:render_coverage_dir] = opts[:dir] || app.opts[:render_coverage_dir] || 'coverage/views'
45
+ coverage_dir = app.opts[:render_coverage_dir] = opts[:dir] || app.render_coverage_dir || 'coverage/views'
46
46
  Dir.mkdir(coverage_dir) unless File.directory?(coverage_dir)
47
47
  end
48
48
 
49
49
  module ClassMethods
50
+ RodaPlugins.opt_attr_reader(self, :render_coverage_strip_paths)
51
+ RodaPlugins.opt_attr_reader(self, :render_coverage_dir)
52
+
50
53
  # Set a compiled path on the created template, if the path for
51
54
  # the template is in one of the allowed_views.
52
55
  def create_template(opts, template_opts)
@@ -54,9 +57,10 @@ class Roda
54
57
 
55
58
  path = File.expand_path(opts[:path])
56
59
  compiled_path = nil
57
- (self.opts[:render_coverage_strip_paths] || render_opts[:allowed_paths]).each do |dir|
58
- if path.start_with?(dir + '/')
59
- compiled_path = File.join(self.opts[:render_coverage_dir], path[dir.length+1, 10000000].tr('/', '-'))
60
+ (render_coverage_strip_paths || render_opts[:allowed_paths]).each do |dir|
61
+ dir += "/" unless dir.end_with?("/")
62
+ if path.start_with?(dir)
63
+ compiled_path = File.join(render_coverage_dir, path[dir.length, 10000000].tr('/', '-'))
60
64
  break
61
65
  end
62
66
  end
@@ -76,9 +80,9 @@ class Roda
76
80
  template = super
77
81
 
78
82
  # Set compiled path for template when using older tilt versions.
79
- # :nocov:
83
+ # simplecov:disable
80
84
  template.compiled_path = compiled_path if compiled_path
81
- # :nocov:
85
+ # simplecov:enable
82
86
 
83
87
  template
84
88
  end
@@ -171,12 +171,12 @@ class Roda
171
171
 
172
172
  File.basename(template).sub(/\..+\z/, '').to_sym
173
173
  end
174
- # :nocov:
174
+ # simplecov:disable
175
175
  else
176
176
  def render_each_default_local(template)
177
177
  File.basename(template.to_s).sub(/\..+\z/, '').to_sym
178
178
  end
179
- # :nocov:
179
+ # simplecov:enable
180
180
  end
181
181
 
182
182
  if Render::COMPILED_METHOD_SUPPORT