roda 3.109.0 → 3.110.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/lib/roda/cache.rb +0 -2
  3. data/lib/roda/opts.rb +1 -1
  4. data/lib/roda/plugins/_symbol_class_matchers.rb +2 -4
  5. data/lib/roda/plugins/additional_view_directories.rb +2 -2
  6. data/lib/roda/plugins/assets.rb +173 -142
  7. data/lib/roda/plugins/assets_preloading.rb +26 -10
  8. data/lib/roda/plugins/backtracking_array.rb +2 -2
  9. data/lib/roda/plugins/branch_locals.rb +4 -4
  10. data/lib/roda/plugins/break.rb +2 -2
  11. data/lib/roda/plugins/caching.rb +27 -17
  12. data/lib/roda/plugins/capture_erb.rb +15 -12
  13. data/lib/roda/plugins/chunked.rb +32 -30
  14. data/lib/roda/plugins/class_level_routing.rb +5 -2
  15. data/lib/roda/plugins/class_matchers.rb +11 -6
  16. data/lib/roda/plugins/common_logger.rb +21 -3
  17. data/lib/roda/plugins/conditional_sessions.rb +3 -3
  18. data/lib/roda/plugins/content_for.rb +7 -6
  19. data/lib/roda/plugins/content_security_policy.rb +11 -12
  20. data/lib/roda/plugins/cookie_flags.rb +12 -6
  21. data/lib/roda/plugins/cookies.rb +7 -5
  22. data/lib/roda/plugins/csrf.rb +2 -1
  23. data/lib/roda/plugins/custom_block_results.rb +6 -7
  24. data/lib/roda/plugins/default_headers.rb +2 -2
  25. data/lib/roda/plugins/delay_build.rb +1 -1
  26. data/lib/roda/plugins/delegate.rb +7 -6
  27. data/lib/roda/plugins/direct_call.rb +5 -1
  28. data/lib/roda/plugins/disallow_file_uploads.rb +2 -3
  29. data/lib/roda/plugins/drop_body.rb +3 -6
  30. data/lib/roda/plugins/each_part.rb +4 -6
  31. data/lib/roda/plugins/early_hints.rb +4 -4
  32. data/lib/roda/plugins/empty_root.rb +5 -5
  33. data/lib/roda/plugins/environments.rb +8 -8
  34. data/lib/roda/plugins/erb_h.rb +4 -4
  35. data/lib/roda/plugins/error_email.rb +16 -17
  36. data/lib/roda/plugins/error_handler.rb +24 -32
  37. data/lib/roda/plugins/error_mail.rb +26 -15
  38. data/lib/roda/plugins/exception_page.rb +41 -35
  39. data/lib/roda/plugins/filter_common_logger.rb +6 -4
  40. data/lib/roda/plugins/flash.rb +16 -13
  41. data/lib/roda/plugins/freeze_request_response.rb +24 -0
  42. data/lib/roda/plugins/hash_routes.rb +1 -1
  43. data/lib/roda/plugins/permissions_policy.rb +14 -16
  44. data/lib/roda/plugins/public.rb +11 -0
  45. data/lib/roda/plugins/recheck_precompiled_assets.rb +30 -8
  46. data/lib/roda/plugins/render.rb +3 -0
  47. data/lib/roda/plugins/render_locals.rb +1 -1
  48. data/lib/roda/plugins/sessions.rb +158 -137
  49. data/lib/roda/plugins/symbol_matchers.rb +8 -5
  50. data/lib/roda/plugins/unescape_path.rb +22 -4
  51. data/lib/roda/plugins.rb +24 -0
  52. data/lib/roda/request.rb +8 -7
  53. data/lib/roda/response.rb +9 -7
  54. data/lib/roda/version.rb +1 -1
  55. data/lib/roda.rb +12 -9
  56. metadata +2 -1
@@ -21,7 +21,7 @@ class Roda
21
21
  #
22
22
  # == Usage
23
23
  #
24
- # When loading the plugin, use the :css and :js options
24
+ # When loading the plugin, use the +:css+ and +:js+ options
25
25
  # to set the source file(s) to use for CSS and javascript assets:
26
26
  #
27
27
  # plugin :assets, css: 'some_file.scss', js: 'some_file.coffee'
@@ -31,7 +31,7 @@ class Roda
31
31
  # assets/css/some_file.scss
32
32
  # assets/js/some_file.coffee
33
33
  #
34
- # The values for the :css and :js options can be arrays to load multiple
34
+ # The values for the +:css+ and +:js+ options can be arrays to load multiple
35
35
  # files. If you want to change the paths where asset files are stored, see the
36
36
  # Options section below.
37
37
  #
@@ -49,7 +49,7 @@ class Roda
49
49
  #
50
50
  # === Views
51
51
  #
52
- # In your layout view, use the assets method to add links to your CSS and
52
+ # In your layout view, use the +assets+ method to add links to your CSS and
53
53
  # javascript assets:
54
54
  #
55
55
  # <%= assets(:css) %>
@@ -66,7 +66,7 @@ class Roda
66
66
  # == Asset Paths
67
67
  #
68
68
  # If you just want the paths rather than the full tags, you can use
69
- # assets_paths instead. This will return an array of the sources that
69
+ # +assets_paths+ instead. This will return an array of the sources that
70
70
  # the assets function would have put into tags:
71
71
  #
72
72
  # assets_paths(:css)
@@ -95,15 +95,14 @@ class Roda
95
95
  # If you do not want to force that directory structure when using
96
96
  # asset groups, you can use the <tt>group_subdirs: false</tt> option.
97
97
  #
98
- # In your view code use an array argument in your call to assets:
98
+ # In your view code use an array argument in your call to +assets+:
99
99
  #
100
100
  # <%= assets([:css, :frontend]) %>
101
101
  #
102
102
  # === Nesting
103
103
  #
104
- # Asset groups also support nesting, though that should only be needed
105
- # in fairly large applications. You can use a nested hash when loading
106
- # the plugin:
104
+ # Asset groups also support nesting. You can use a nested hash when
105
+ # loading the plugin:
107
106
  #
108
107
  # plugin :assets,
109
108
  # css: {frontend: {dashboard: 'some_frontend_file.scss'}}
@@ -115,13 +114,13 @@ class Roda
115
114
  # == Caching
116
115
  #
117
116
  # The assets plugin uses the caching plugin internally, and will set the
118
- # Last-Modified header to the modified timestamp of the asset source file
117
+ # <tt>Last-Modified</tt> header to the modified timestamp of the asset source file
119
118
  # when rendering the asset.
120
119
  #
121
120
  # If you have assets that include other asset files, such as using @import
122
121
  # in a sass file, you need to specify the dependencies for your assets so
123
122
  # that the assets plugin will correctly pick up changes. You can do this
124
- # using the :dependencies option to the plugin, which takes a hash where
123
+ # using the +:dependencies+ option to the plugin, which takes a hash where
125
124
  # the keys are paths to asset files, and values are arrays of paths to
126
125
  # dependencies of those asset files:
127
126
  #
@@ -131,26 +130,22 @@ class Roda
131
130
  # == Asset Compilation
132
131
  #
133
132
  # In production, you are generally going to want to compile your assets
134
- # into a single file, with you can do by calling compile_assets after
133
+ # into a single file, with you can do by calling +compile_assets+ after
135
134
  # loading the plugin:
136
135
  #
137
136
  # plugin :assets, css: 'some_file.scss', js: 'some_file.coffee'
138
137
  # compile_assets
139
138
  #
140
- # After calling compile_assets, calls to assets in your views will default
139
+ # After calling +compile_assets+, calls to +assets+ in your views will default
141
140
  # to a using a single link each to your CSS and javascript compiled asset
142
141
  # files. By default the compiled files are written to the public directory,
143
142
  # so that they can be served by the webserver.
144
143
  #
145
144
  # === Asset Compression
146
145
  #
147
- # If you have the yuicompressor gem installed and working, it will be used
148
- # automatically to compress your javascript and css assets. For javascript
149
- # assets, if yuicompressor is not available, the plugin will check for
150
- # closure-compiler, uglifier, and minjs and use the first one that works.
151
- # If no compressors are available, the assets will just be concatenated
152
- # together and not compressed during compilation. You can use the
153
- # :css_compressor and :js_compressor options to specify the compressor to use.
146
+ # You can use the +:css_compressor+ and +:js_compressor+ options to specify a
147
+ # compressor callable. The callable will be called with the uncompressed source,
148
+ # and should return the compressed source.
154
149
  #
155
150
  # It is also possible to use the built-in compression options in the CSS or JS
156
151
  # compiler, assuming the compiler supports such options. For example, with
@@ -159,9 +154,7 @@ class Roda
159
154
  # plugin :assets,
160
155
  # css_opts: {style: :compressed}
161
156
  #
162
- # In Roda 4, automatic compression of assets will not be attempted. You will
163
- # need to specify the compressor to use using a Proc or Method. Here are values
164
- # that you can use.
157
+ # Here are examples you can use for a few compressor libraries:
165
158
  #
166
159
  # ==== yui
167
160
  #
@@ -222,19 +215,19 @@ class Roda
222
215
  # end
223
216
  #
224
217
  # If you do have the application serve the compiled assets, it will use the
225
- # Last-Modified header to make sure that clients do not redownload compiled
218
+ # <tt>Last-Modified</tt> header to make sure that clients do not redownload compiled
226
219
  # assets that haven't changed.
227
220
  #
228
221
  # === Asset Precompilation
229
222
  #
230
223
  # If you want to precompile your assets, so they do not need to be compiled
231
- # every time you boot the application, you can provide a :precompiled option
224
+ # every time you boot the application, you can provide a +:precompiled+ option
232
225
  # when loading the plugin. The value of this option should be the filename
233
226
  # where the compiled asset metadata is stored.
234
227
  #
235
228
  # If the compiled asset metadata file does not exist when the assets plugin
236
229
  # is loaded, the plugin will run in non-compiled mode. However, when you call
237
- # compile_assets, it will write the compiled asset metadata file after
230
+ # +compile_assets+, it will write the compiled asset metadata file after
238
231
  # compiling the assets.
239
232
  #
240
233
  # If the compiled asset metadata file already exists when the assets plugin
@@ -245,7 +238,7 @@ class Roda
245
238
  # ==== On Heroku
246
239
  #
247
240
  # Heroku supports precompiling the assets when using Roda. You just need to
248
- # add an assets:precompile task, similar to this:
241
+ # add an assets:precompile task, such as:
249
242
  #
250
243
  # namespace :assets do
251
244
  # desc "Precompile the assets"
@@ -283,71 +276,67 @@ class Roda
283
276
  # path, you have the following options:
284
277
  #
285
278
  # * Copy, hard link, or symlink the external assets files into the assets path.
286
- # * Use tilt-indirect or another method of indirection (such as an erb template that loads
287
- # the external asset file) so that a file inside the assets path can reference files
288
- # outside the assets path.
289
- #
290
- # == Plugin Options
291
- #
292
- # :add_suffix :: Whether to append a .css or .js extension to asset routes in non-compiled mode
293
- # (default: false)
294
- # :compiled_asset_host :: The asset host to use for compiled assets. Should include the protocol
295
- # as well as the host (e.g. "https://cdn.example.com", "//cdn.example.com")
296
- # :compiled_css_dir :: Directory name in which to store the compiled css file,
279
+ # * Use indirection, such as an erb template that loads the external asset file,
280
+ # so that a file inside the assets path can reference files outside the assets path.
281
+ #
282
+ # == Plugin Keyword Arguments
283
+ #
284
+ # +add_suffix+ :: Whether to append a .css or .js extension to asset routes in non-compiled mode
285
+ # (default: false)
286
+ # +compiled_asset_host+ :: The asset host to use for compiled assets. Should include the protocol
287
+ # as well as the host (e.g. "https://cdn.example.com", "//cdn.example.com")
288
+ # +compiled_css_dir+ :: Directory name in which to store the compiled css file,
289
+ # inside :compiled_path (default: nil)
290
+ # +compiled_css_route+ :: Route under :prefix for compiled css assets (default: :compiled_css_dir)
291
+ # +compiled_js_dir+ :: Directory name in which to store the compiled javascript file,
297
292
  # inside :compiled_path (default: nil)
298
- # :compiled_css_route :: Route under :prefix for compiled css assets (default: :compiled_css_dir)
299
- # :compiled_js_dir :: Directory name in which to store the compiled javascript file,
300
- # inside :compiled_path (default: nil)
301
- # :compiled_js_route :: Route under :prefix for compiled javscript assets (default: :compiled_js_dir)
302
- # :compiled_name :: Compiled file name prefix (default: 'app')
303
- # :compiled_path:: Path inside public folder in which compiled files are stored (default: :prefix)
304
- # :concat_only :: Whether to just concatenate instead of concatenating
305
- # and compressing files (default: false)
306
- # :css_compressor :: Compressor to use for compressing CSS, either a Proc, Method, :yui, :none, or nil (the default, which will try
307
- # :yui if available, but not fail if it is not available)
308
- # :css_dir :: Directory name containing your css source, inside :path (default: 'css')
309
- # :css_headers :: A hash of additional headers for your rendered css files
310
- # :css_opts :: Template options to pass to the render plugin (via :template_opts) when rendering css assets
311
- # :css_route :: Route under :prefix for css assets (default: :css_dir)
312
- # :dependencies :: A hash of dependencies for your asset files. Keys should be paths to asset files,
313
- # values should be arrays of paths your asset files depends on. This is used to
314
- # detect changes in your asset files.
315
- # :early_hints :: Automatically send early hints for all assets. Requires the early_hints plugin.
316
- # :group_subdirs :: Whether a hash used in :css and :js options requires the assets for the
317
- # related group are contained in a subdirectory with the same name (default: true)
318
- # :gzip :: Store gzipped compiled assets files, and serve those to clients who accept gzip encoding.
319
- # :headers :: A hash of additional headers for both js and css rendered files
320
- # :js_compressor :: Compressor to use for compressing javascript, either a Proc, Method, :yui, :closure, :uglifier, :minjs,
321
- # :none, or nil (the default, which will try :yui, :closure, :uglifier, then :minjs, but
322
- # not fail if any of them is not available)
323
- # :js_dir :: Directory name containing your javascript source, inside :path (default: 'js')
324
- # :js_headers :: A hash of additional headers for your rendered javascript files
325
- # :js_opts :: Template options to pass to the render plugin (via :template_opts) when rendering javascript assets
326
- # :js_route :: Route under :prefix for javascript assets (default: :js_dir)
327
- # :path :: Path to your asset source directory (default: 'assets'). Relative
293
+ # +compiled_js_route+ :: Route under :prefix for compiled javscript assets (default: :compiled_js_dir)
294
+ # +compiled_name+ :: Compiled file name prefix (default: 'app')
295
+ # +compiled_path+ :: Path inside public folder in which compiled files are stored (default: :prefix)
296
+ # +css+ :: The files contain the CSS assets, or a hash to use asset groups.
297
+ # +css_compressor+ :: Compressor callable to use for compressing CSS
298
+ # +css_dir+ :: Directory name containing your css source, inside :path (default: 'css')
299
+ # +css_headers+ :: A hash of additional headers for your rendered css files
300
+ # +css_opts+ :: Template options to pass to the render plugin (via :template_opts) when rendering css assets
301
+ # +css_route+ :: Route under :prefix for css assets (default: :css_dir)
302
+ # +dependencies+ :: A hash of dependencies for your asset files. Keys should be paths to asset files,
303
+ # values should be arrays of paths your asset files depends on. This is used to
304
+ # detect changes in your asset files.
305
+ # +early_hints+ :: Automatically send early hints for all assets. Requires the early_hints plugin.
306
+ # +group_subdirs+ :: Whether a hash used in :css and :js options requires the assets for the
307
+ # related group are contained in a subdirectory with the same name (default: true)
308
+ # +gzip+ :: Store gzipped compiled assets files, and serve those to clients who accept gzip encoding.
309
+ # +headers+ :: A hash of additional headers for both js and css rendered files
310
+ # +js+ :: The files contain the javascript assets, or a hash to use assets groups.
311
+ # +js_compressor+ :: Compressor callable to use for compressing javascript
312
+ # +js_dir+ :: Directory name containing your javascript source, inside :path (default: 'js')
313
+ # +js_headers+ :: A hash of additional headers for your rendered javascript files
314
+ # +js_opts+ :: Template options to pass to the render plugin (via :template_opts) when rendering javascript assets
315
+ # +js_route+ :: Route under :prefix for javascript assets (default: :js_dir)
316
+ # +path+ :: Path to your asset source directory (default: 'assets'). Relative
328
317
  # paths will be considered relative to the application's :root option.
329
- # :postprocessor :: A block which should accept three arguments (asset name, asset type,
330
- # content). This block can be used to hook into the asset system and
331
- # make your own modifications before the asset is served. If the asset
332
- # is to be compiled, the block is called at compile time.
333
- # :prefix :: Prefix for assets path in your URL/routes (default: 'assets')
334
- # :precompiled :: Path to the compiled asset metadata file. If the file exists, will use compiled
335
- # mode using the metadata in the file. If the file does not exist, will use
336
- # non-compiled mode, but will write the metadata to the file if compile_assets is called.
337
- # :public :: Path to your public folder, in which compiled files are placed (default: 'public'). Relative
338
- # paths will be considered relative to the application's :root option.
339
- # :relative_paths :: Use relative paths instead of absolute paths when setting up link and script tags for
340
- # assets.
341
- # :sri :: Enables subresource integrity when setting up references to compiled assets. The value should be
342
- # :sha256, :sha384, or :sha512 depending on which hash algorithm you want to use. This changes the
343
- # hash algorithm that Roda will use when naming compiled asset files. The default is :sha256, you
344
- # can use nil to disable subresource integrity.
345
- # :timestamp_paths :: Include the timestamp of assets in asset paths in non-compiled mode. Doing this can
346
- # slow down development requests due to additional requests to get last modified times,
347
- # but it will make sure the paths change in development when there are modifications,
348
- # which can fix issues when using a caching proxy in non-compiled mode. This can also
349
- # be specified as a string to use that string to separate the timestamp from the asset.
350
- # By default, <tt>/</tt> is used as the separator if timestamp paths are enabled.
318
+ # +postprocessor+ :: A block which should accept three arguments (asset name, asset type,
319
+ # content). This block can be used to hook into the asset system and
320
+ # make your own modifications before the asset is served. If the asset
321
+ # is to be compiled, the block is called at compile time.
322
+ # +prefix+ :: Prefix for assets path in your URL/routes (default: 'assets')
323
+ # +precompiled+ :: Path to the compiled asset metadata file. If the file exists, will use compiled
324
+ # mode using the metadata in the file. If the file does not exist, will use
325
+ # non-compiled mode, but will write the metadata to the file if compile_assets is called.
326
+ # +public+ :: Path to your public folder, in which compiled files are placed (default: 'public').
327
+ # Relative paths will be considered relative to the application's :root option.
328
+ # +relative_paths+ :: Use relative paths instead of absolute paths when setting up link and script tags for
329
+ # assets.
330
+ # +sri+ :: Enables subresource integrity when setting up references to compiled assets. The value should be
331
+ # :sha256, :sha384, or :sha512 depending on which hash algorithm you want to use. This changes the
332
+ # hash algorithm that Roda will use when naming compiled asset files. The default is :sha256, you
333
+ # can use nil to disable subresource integrity.
334
+ # +timestamp_paths+ :: Include the timestamp of assets in asset paths in non-compiled mode. Doing this can
335
+ # slow down development requests due to additional requests to get last modified times,
336
+ # but it will make sure the paths change in development when there are modifications,
337
+ # which can fix issues when using a caching proxy in non-compiled mode. This can also
338
+ # be specified as a string to use that string to separate the timestamp from the asset.
339
+ # By default, <tt>/</tt> is used as the separator if timestamp paths are enabled.
351
340
  module Assets
352
341
  DEFAULTS = {
353
342
  :compiled_name => 'app'.freeze,
@@ -364,10 +353,16 @@ class Roda
364
353
  :compiled_js_dir => nil,
365
354
  :sri => :sha256
366
355
  }.freeze
356
+ RodaPlugins.deprecate_constant(self, :DEFAULTS)
367
357
 
368
358
  # Internal exception raised when a compressor cannot be found
369
359
  CompressorNotFound = Class.new(RodaError)
370
360
 
361
+ PLUGIN_ARGUMENTS = %i[add_suffix compiled_asset_host compiled_css_dir compiled_css_route compiled_js_dir compiled_js_route compiled_name compiled_path concat_only css css_compressor css_dir css_headers css_opts css_route dependencies early_hints group_subdirs gzip headers js js_compressor js_dir js_headers js_opts js_route path postprocessor prefix precompiled public relative_paths sri timestamp_paths].freeze
362
+ private_constant :PLUGIN_ARGUMENTS
363
+
364
+ Config = Struct.new(*PLUGIN_ARGUMENTS, :compiled, :compiled_css_path, :compiled_css_prefix, :compiled_css_suffix, :compiled_js_path, :compiled_js_prefix, :compiled_js_suffix, :css_path, :css_prefix, :css_suffix, :expanded_dependencies, :js_path, :js_prefix, :js_suffix, :orig_opts)
365
+
371
366
  # Load the render, caching, and h plugins, since the assets plugin
372
367
  # depends on them.
373
368
  def self.load_dependencies(app, opts = OPTS)
@@ -380,14 +375,20 @@ class Roda
380
375
  if opts[:relative_paths]
381
376
  app.plugin :relative_path
382
377
  end
378
+
379
+ if opts[:early_hints]
380
+ app.plugin :early_hints
381
+ end
383
382
  end
384
383
 
385
384
  # Setup the options for the plugin. See the Assets module RDoc
386
385
  # for a description of the supported options.
387
- def self.configure(app, opts = {})
386
+ def self.configure(app, opts=OPTS)
387
+ RodaPlugins.check_supported_opts("the assets plugin", opts, PLUGIN_ARGUMENTS)
388
+
388
389
  if app.assets_opts
389
390
  prev_opts = app.assets_opts[:orig_opts]
390
- orig_opts = app.assets_opts[:orig_opts].merge(opts)
391
+ orig_opts = prev_opts.merge(opts)
391
392
  [:headers, :css_headers, :js_headers, :css_opts, :js_opts, :dependencies].each do |s|
392
393
  if prev_opts[s]
393
394
  if opts[s]
@@ -425,17 +426,27 @@ class Roda
425
426
  opts[:compiled] = app.send(:_precompiled_asset_metadata, opts[:precompiled])
426
427
  end
427
428
 
428
- if opts[:early_hints]
429
- app.plugin :early_hints
430
- end
431
-
432
429
  if opts[:timestamp_paths] && !opts[:timestamp_paths].is_a?(String)
433
430
  opts[:timestamp_paths] = '/'
434
431
  end
435
432
 
436
- DEFAULTS.each do |k, v|
437
- opts[k] = v unless opts.has_key?(k)
438
- end
433
+ {
434
+ :compiled_name => 'app'.freeze,
435
+ :js_dir => 'js'.freeze,
436
+ :css_dir => 'css'.freeze,
437
+ :prefix => 'assets'.freeze,
438
+ :concat_only => false,
439
+ :compiled => false,
440
+ :add_suffix => false,
441
+ :early_hints => false,
442
+ :timestamp_paths => false,
443
+ :group_subdirs => true,
444
+ :compiled_css_dir => nil,
445
+ :compiled_js_dir => nil,
446
+ :sri => :sha256
447
+ }.each do |k, v|
448
+ opts[k] = v unless opts.has_key?(k)
449
+ end
439
450
 
440
451
  [
441
452
  [:compiled_path, :prefix],
@@ -496,10 +507,12 @@ class Roda
496
507
  end
497
508
 
498
509
  opts.freeze
510
+ app.opts[:assets_config] = Config.new(*opts.values_at(*Config.members)).freeze
499
511
  end
500
512
 
501
513
  module ClassMethods
502
514
  RodaPlugins.opt_attr_reader(self, :assets, name: :assets_opts)
515
+ RodaPlugins.opt_attr_reader(self, :assets_config)
503
516
 
504
517
  # Compile options for the given asset type. If no asset_type
505
518
  # is given, compile both the :css and :js asset types. You
@@ -508,9 +521,7 @@ class Roda
508
521
  def compile_assets(type=nil)
509
522
  require 'fileutils'
510
523
 
511
- unless assets_opts[:compiled]
512
- opts[:assets] = assets_opts.merge(:compiled => _compiled_assets_initial_hash).freeze
513
- end
524
+ _update_compiled_assets(_compiled_assets_initial_hash) unless assets_config.compiled
514
525
 
515
526
  if type == nil
516
527
  _compile_assets(:css)
@@ -519,18 +530,23 @@ class Roda
519
530
  _compile_assets(type)
520
531
  end
521
532
 
522
- if precompile_file = assets_opts[:precompiled]
533
+ if precompile_file = assets_config.precompiled
523
534
  ::FileUtils.mkdir_p(File.dirname(precompile_file))
524
535
  tmp_file = "#{precompile_file}.tmp"
525
- ::File.open(tmp_file, 'wb'){|f| f.write(json_serializer.call(assets_opts[:compiled]))}
536
+ ::File.open(tmp_file, 'wb'){|f| f.write(json_serializer.call(assets_config.compiled))}
526
537
  ::File.rename(tmp_file, precompile_file)
527
538
  end
528
539
 
529
- assets_opts[:compiled]
540
+ assets_config.compiled
530
541
  end
531
542
 
532
543
  private
533
544
 
545
+ def _update_compiled_assets(compiled)
546
+ opts[:assets] = assets_opts.merge(:compiled => compiled).freeze
547
+ opts[:assets_config] = Config.new(*assets_opts.values_at(*Config.members)).freeze
548
+ end
549
+
534
550
  # The initial hash to use to store compiled asset metadata.
535
551
  def _compiled_assets_initial_hash
536
552
  {}
@@ -541,7 +557,7 @@ class Roda
541
557
  def _compile_assets(type)
542
558
  type, *dirs = type if type.is_a?(Array)
543
559
  dirs ||= []
544
- files = assets_opts[type]
560
+ files = assets_config[type]
545
561
  dirs.each{|d| files = files[d]}
546
562
 
547
563
  case files
@@ -563,17 +579,17 @@ class Roda
563
579
  # are files in an asset group.
564
580
  def compile_assets_files(files, type, dirs)
565
581
  dirs = nil if dirs && dirs.empty?
566
- o = assets_opts
582
+ o = assets_config
567
583
  app = allocate
568
584
 
569
585
  content = files.map do |file|
570
- file = "#{dirs.join('/')}/#{file}" if dirs && o[:group_subdirs]
586
+ file = "#{dirs.join('/')}/#{file}" if dirs && o.group_subdirs
571
587
  file = "#{o[:"#{type}_path"]}#{file}"
572
588
  app.read_asset_file(file, type)
573
589
  end.join("\n")
574
590
 
575
591
  # RODA4: Remove :concat_only option
576
- unless o[:concat_only]
592
+ unless o.concat_only
577
593
  content = compress_asset(content, type)
578
594
  end
579
595
 
@@ -597,7 +613,7 @@ class Roda
597
613
  # Compress the given content for the given type by using the
598
614
  # configured compressor, or trying the supported compressors.
599
615
  def compress_asset(content, type)
600
- case compressor = assets_opts[:"#{type}_compressor"]
616
+ case compressor = assets_config[:"#{type}_compressor"]
601
617
  when :none
602
618
  return content
603
619
  when nil
@@ -682,7 +698,7 @@ class Roda
682
698
  # a different digest type or to return a static string if you don't
683
699
  # want to use a unique value.
684
700
  def asset_digest(content)
685
- algo = assets_opts[:sri] || :sha256
701
+ algo = assets_config.sri || :sha256
686
702
  digest = begin
687
703
  require 'openssl'
688
704
  ::OpenSSL::Digest
@@ -700,7 +716,7 @@ class Roda
700
716
  # Return an array of paths for the given asset type and optionally
701
717
  # asset group. See the assets function documentation for details.
702
718
  def assets_paths(type)
703
- o = self.class.assets_opts
719
+ o = self.class.assets_config
704
720
  if type.is_a?(Array)
705
721
  ltype, *dirs = type
706
722
  else
@@ -709,23 +725,29 @@ class Roda
709
725
  stype = ltype.to_s
710
726
 
711
727
  url_prefix = request.script_name if self.class.add_script_name?
712
- relative_paths = o[:relative_paths]
728
+ relative_paths = o.relative_paths
713
729
 
714
- paths = if o[:compiled]
715
- relative_paths = false if o[:compiled_asset_host]
730
+ paths = if o.compiled
731
+ relative_paths = false if o.compiled_asset_host
716
732
  if ukey = _compiled_assets_hash(type, true)
717
- ["#{o[:compiled_asset_host]}#{url_prefix}/#{o[:"compiled_#{stype}_prefix"]}.#{ukey}.#{stype}"]
733
+ ["#{o.compiled_asset_host}#{url_prefix}/#{o[:"compiled_#{stype}_prefix"]}.#{ukey}.#{stype}"]
718
734
  else
719
735
  []
720
736
  end
721
737
  else
722
- asset_dir = o[ltype]
738
+ case ltype
739
+ when :css, :js
740
+ asset_dir = o[ltype]
741
+ else
742
+ RodaPlugins.warn("Invalid asset type given to assets_paths, such be :css or :js: #{ltype}", uplevel: 1)
743
+ end
744
+
723
745
  if dirs && !dirs.empty?
724
746
  dirs.each{|f| asset_dir = asset_dir[f]}
725
- prefix = "#{dirs.join('/')}/" if o[:group_subdirs]
747
+ prefix = "#{dirs.join('/')}/" if o.group_subdirs
726
748
  end
727
749
  Array(asset_dir).map do |f|
728
- if ts = o[:timestamp_paths]
750
+ if ts = o.timestamp_paths
729
751
  mtime = asset_last_modified(File.join(o[:"#{stype}_path"], *[prefix, f].compact))
730
752
  mtime = "#{sprintf("%i%06i", mtime.to_i, mtime.usec)}#{ts}"
731
753
  end
@@ -743,11 +765,11 @@ class Roda
743
765
  end
744
766
 
745
767
  # Return a string containing html tags for the given asset type.
746
- # This will use a script tag for the :js type and a link tag for
747
- # the :css type.
768
+ # This will use a script tag for the +:js+ type and a link tag for
769
+ # the +:css+ type.
748
770
  #
749
771
  # To return the tags for a specific asset group, use an array for
750
- # the type, such as [:css, :frontend].
772
+ # the type, such as <tt>[:css, :frontend]</tt>.
751
773
  #
752
774
  # You can specify custom attributes for the tag by passing a hash
753
775
  # as the attrs argument.
@@ -758,8 +780,8 @@ class Roda
758
780
  def assets(type, attrs = OPTS)
759
781
  ltype = type.is_a?(Array) ? type[0] : type
760
782
 
761
- o = self.class.assets_opts
762
- if o[:compiled] && (algo = o[:sri]) && (hash = _compiled_assets_hash(type))
783
+ o = self.class.assets_config
784
+ if o.compiled && (algo = o.sri) && (hash = _compiled_assets_hash(type))
763
785
  attrs = Hash[attrs]
764
786
  attrs[:integrity] = "#{algo}-#{h([[hash].pack('H*')].pack('m').tr("\n", ''))}"
765
787
  end
@@ -775,13 +797,13 @@ class Roda
775
797
  end
776
798
 
777
799
  paths = assets_paths(type)
778
- if o[:early_hints]
800
+ if o.early_hints
779
801
  early_hint_as = ltype == :js ? 'script' : 'style'
780
802
  early_hints = paths.map{|p| "<#{p}>; rel=preload; as=#{early_hint_as}"}
781
803
  early_hints = early_hints.join("\n") if Rack.release < '3'
782
804
  send_early_hints(RodaResponseHeaders::LINK=>early_hints)
783
805
  end
784
- paths.map{|p| "#{tag_start}#{h(p)}#{tag_end}"}.join("\n")
806
+ paths.map!{|p| "#{tag_start}#{h(p)}#{tag_end}"}.join("\n")
785
807
  end
786
808
 
787
809
  # Render the asset with the given filename. When assets are compiled,
@@ -792,11 +814,11 @@ class Roda
792
814
  # In both cases, if the file has not been modified since the last request,
793
815
  # this will return a 304 response.
794
816
  def render_asset(file, type)
795
- o = self.class.assets_opts
796
- if o[:compiled]
817
+ o = self.class.assets_config
818
+ if o.compiled
797
819
  file = "#{o[:"compiled_#{type}_path"]}#{file}"
798
820
 
799
- if o[:gzip] && env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/
821
+ if o.gzip && env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/
800
822
  @_response[RodaResponseHeaders::CONTENT_ENCODING] = 'gzip'
801
823
  file += '.gz'
802
824
  end
@@ -814,21 +836,21 @@ class Roda
814
836
  # Otherwise, render the file using the render plugin. +file+ should be
815
837
  # the relative path to the file from the current directory.
816
838
  def read_asset_file(file, type)
817
- o = self.class.assets_opts
839
+ o = self.class.assets_config
818
840
 
819
841
  content = if file.end_with?(".#{type}")
820
842
  ::File.read(file)
821
843
  else
822
- render_asset_file(file, :template_opts=>o[:"#{type}_opts"], :dependencies=>o[:expanded_dependencies][file])
844
+ render_asset_file(file, :template_opts=>o[:"#{type}_opts"], :dependencies=>o.expanded_dependencies[file])
823
845
  end
824
846
 
825
- o[:postprocessor] ? o[:postprocessor].call(file, type, content) : content
847
+ o.postprocessor ? o.postprocessor.call(file, type, content) : content
826
848
  end
827
849
 
828
850
  private
829
851
 
830
852
  def _compiled_assets_hash(type, return_ukey=false)
831
- compiled = self.class.assets_opts[:compiled]
853
+ compiled = self.class.assets_config.compiled
832
854
  type, *dirs = type if type.is_a?(Array)
833
855
  stype = type.to_s
834
856
 
@@ -849,8 +871,8 @@ class Roda
849
871
  # other files, check the modification times of all dependencies and
850
872
  # return the maximum.
851
873
  def asset_last_modified(file)
852
- if deps = self.class.assets_opts[:expanded_dependencies][file]
853
- ([file] + Array(deps)).map{|f| ::File.stat(f).mtime}.max
874
+ if deps = self.class.assets_config.expanded_dependencies[file]
875
+ [file, *deps].map!{|f| ::File.stat(f).mtime}.max
854
876
  else
855
877
  ::File.stat(file).mtime
856
878
  end
@@ -861,7 +883,7 @@ class Roda
861
883
  # type-specific headers.
862
884
  def check_asset_request(file, type, mtime)
863
885
  @_request.last_modified(mtime)
864
- @_response.headers.merge!(self.class.assets_opts[:"#{type}_headers"])
886
+ @_response.headers.merge!(self.class.assets_config[:"#{type}_headers"])
865
887
  end
866
888
 
867
889
  # Render the given asset file using the render plugin, with the given options.
@@ -875,15 +897,24 @@ class Roda
875
897
  # An array of asset type strings and regexps for that type, for all asset types
876
898
  # handled.
877
899
  def assets_matchers
878
- @assets_matchers ||= [:css, :js].map do |t|
900
+ @assets_matchers ||= _build_assets_matchers
901
+ end
902
+
903
+ def freeze
904
+ assets_matchers
905
+ super
906
+ end
907
+
908
+ private
909
+
910
+ def _build_assets_matchers
911
+ [:css, :js].map! do |t|
879
912
  if regexp = assets_regexp(t)
880
913
  [t, regexp].freeze
881
914
  end
882
915
  end.compact.freeze
883
916
  end
884
917
 
885
- private
886
-
887
918
  # A string for the asset filename for the asset type, key, and digest.
888
919
  def _asset_regexp(type, key, digest)
889
920
  "#{key.sub(/\A#{type}/, '')}.#{digest}.#{type}"
@@ -892,8 +923,8 @@ class Roda
892
923
  # The regexp matcher to use for the given type. This handles any asset groups
893
924
  # for the asset types.
894
925
  def assets_regexp(type)
895
- o = roda_class.assets_opts
896
- if compiled = o[:compiled]
926
+ o = roda_class.assets_config
927
+ if compiled = o.compiled
897
928
  assets = compiled.
898
929
  select{|k,_| k =~ /\A#{type}/}.
899
930
  map{|k, md| _asset_regexp(type, k, md)}
@@ -902,7 +933,7 @@ class Roda
902
933
  else
903
934
  return unless assets = o[type]
904
935
  assets = unnest_assets_hash(assets)
905
- ts = o[:timestamp_paths]
936
+ ts = o.timestamp_paths
906
937
  /#{o[:"#{type}_prefix"]}#{"\\d+#{ts}" if ts}(#{Regexp.union(assets.uniq)})#{o[:"#{type}_suffix"]}/
907
938
  end
908
939
  end
@@ -914,7 +945,7 @@ class Roda
914
945
  when Hash
915
946
  h.flat_map do |k,v|
916
947
  assets = unnest_assets_hash(v)
917
- assets = assets.map{|x| "#{k}/#{x}"} if roda_class.assets_opts[:group_subdirs]
948
+ assets = assets.map{|x| "#{k}/#{x}"} if roda_class.assets_config.group_subdirs
918
949
  assets
919
950
  end
920
951
  else