roda 3.37.0 → 3.42.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de8c769572875e477058b48557b22861b9de731fa3b00b20a3022a67c929609d
4
- data.tar.gz: 5cacc0d7b988bd3dc5bafbc30fcac145f6f67ddcc0e7809e1fc9cd60d8a16273
3
+ metadata.gz: e2bdd27a69c76000c9c1d2d8f37f9919145fdae55224706c78adec42850c5d21
4
+ data.tar.gz: 8b92e1dbc9ec83ebca5d2d5b068c67fa282dfee25fe241451c93228a406a4749
5
5
  SHA512:
6
- metadata.gz: 1bf7df28d82f31820cd19c29b5a3f0f3afbbae2c41cf05974b16698c7ffb1725e924491874ddbb3f355a88990d4aab6d5b5b38f2d9c7612a548bfe0e3701ac2e
7
- data.tar.gz: 32fca636d6324d29958cbcdf5c949c413eaeda7b74a701032d5766a60ec6b3f2db7ec262268dac697c3b3f5f9b183e127ddf265c7ba5015578969bc7f8be02a2
6
+ metadata.gz: 5c9f2d3f0f021b7b0a16628cefe28c4d4001f2a80804f2bde9b48b641a70482a5d13d699252cb9f7c747de50710b91df30f893f83263e3e73c874621948b4cc6
7
+ data.tar.gz: c060f123bfb7bd0f02a83d864169d7d2a021c14cd152b8f3e7641c4c7e0317affb84fce0812afcb0d382e0828e31e2fd97b46482338ae92574fbad97793c8996
data/CHANGELOG CHANGED
@@ -1,3 +1,43 @@
1
+ = 3.42.0 (2021-03-12)
2
+
3
+ * Make Roda.plugin support plugins using keyword arguments in Ruby 3 (jeremyevans)
4
+
5
+ * Make Roda.use support middleware using keyword arguments in Ruby 3 (pat) (#207)
6
+
7
+ * Support common_logger plugin :method option for specifying the method to call on the logger (fnordfish, jeremyevans) (#206)
8
+
9
+ * Add recheck_precompiled_assets plugin for checking for updates to the precompiled asset metadata file (jeremyevans)
10
+
11
+ * Make compile_assets class method in assets plugin use an atomic approach to writing precompiled metadata file (jeremyevans)
12
+
13
+ = 3.41.0 (2021-02-17)
14
+
15
+ * Improve view performance with :content option up to 3x by calling compiled template methods directly (jeremyevans)
16
+
17
+ = 3.40.0 (2021-01-14)
18
+
19
+ * Add freeze_template_caches! to the precompile_templates plugin, which ensures all templates are precompiled, and speeds up template access (jeremyevans)
20
+
21
+ * Add precompile_views to the precompile_templates plugin, which precompiles the optimized render methods (jeremyevans)
22
+
23
+ * Have RodaCache#freeze return the frozen internal hash (which no longer needs a mutex for thread-safety) (jeremyevans)
24
+
25
+ * Speed up the view method in the render plugin even more when freezing the application (jeremyevans)
26
+
27
+ * Speed up the view method in the render plugin when called with a single argument (jeremyevans)
28
+
29
+ = 3.39.0 (2020-12-15)
30
+
31
+ * Speed up relative_path plugin if relative_path or relative_prefix is called more than once (jeremyevans)
32
+
33
+ * Avoid method redefinition warnings in verbose warning mode (jeremyevans)
34
+
35
+ * Make typecast_params.convert! handle explicit nil values the same as missing values (jeremyevans)
36
+
37
+ = 3.38.0 (2020-11-16)
38
+
39
+ * Make error_email and error_mail plugins rescue invalid parameter errors when preparing the email body (jeremyevans)
40
+
1
41
  = 3.37.0 (2020-10-16)
2
42
 
3
43
  * Add custom_matchers plugin, for supporting arbitrary objects as matchers (jeremyevans)
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014-2020 Jeremy Evans
1
+ Copyright (c) 2014-2021 Jeremy Evans
2
2
  Copyright (c) 2010-2014 Michel Martens, Damian Janowski and Cyril David
3
3
  Copyright (c) 2008-2009 Christian Neukirchen
4
4
 
@@ -248,7 +248,7 @@
248
248
  Note that if there are multiple conversion errors raised inside a
249
249
  convert! or convert_each! block, they are recorded and a single
250
250
  Roda::RodaPlugins::TypecastParams::Error instance is raised after
251
- processing the block. TypecastParams::Error#params_names can be
251
+ processing the block. TypecastParams::Error#param_names can be
252
252
  called on the exception to get an array of all parameter names
253
253
  with conversion issues, and TypecastParams::Error#all_errors
254
254
  can be used to get an array of all Error instances.
@@ -0,0 +1,5 @@
1
+ = Improvements
2
+
3
+ * The error_email and error_mail plugins now rescue invalid parameter
4
+ errors when preparing the email body, because you generally don't
5
+ want your error handler to raise an exception.
@@ -0,0 +1,16 @@
1
+ = Improvements
2
+
3
+ * The relative_path plugin is now faster if you are calling
4
+ relative_path or relative_prefix more than once when handling a
5
+ request.
6
+
7
+ * The typecast_params.convert! method in the typecast_params plugin
8
+ now handles explicit nil values the same as missing values.
9
+ Explicit nil values do not generally occur in normal Rack parameter
10
+ parsing, but they can occur when using the json_parser plugin to
11
+ parse JSON requests.
12
+
13
+ * Roda now avoids method redefinition warnings in verbose mode by
14
+ using a self alias. As Ruby 3 is dropping uninitialized instance
15
+ variable warnings, Roda will be verbose warning free if you are
16
+ using Ruby 3.
@@ -0,0 +1,24 @@
1
+ = New Features
2
+
3
+ * A precompile_views method has been added to the
4
+ precompile_templates plugin. This method works with Roda's
5
+ optimized compiled view methods, allowing additional memory
6
+ sharing between parent and child processes.
7
+
8
+ * A freeze_template_caches! method has been added to the
9
+ precompile_templates plugin. This freezes the template caches,
10
+ preventing the compilation of additional templates, useful for
11
+ enforcing that only precompiled templates are used. Additionally,
12
+ this speeds up access to the template caches.
13
+
14
+ * RodaCache#freeze now returns the frozen internal hash, which can
15
+ then be accessed without a mutex. Previously, freeze only froze
16
+ the receiver and not the internal hash, so it didn't have the
17
+ expected effect.
18
+
19
+ = Other Improvements
20
+
21
+ * The view method in the render plugin is now faster in most cases
22
+ when a single argument is used. When freezing the application,
23
+ an additional optimization is performed to increase the
24
+ performance of the view method even further.
@@ -0,0 +1,9 @@
1
+ = Improvements
2
+
3
+ * The performance of the render plugin's view method when passed the
4
+ :content option and no other options or arguments has been improved
5
+ by about 3x, by calling compiled template methods directly.
6
+
7
+ * The compiled template method for the layout is cleared when the
8
+ render plugin is loaded again, which can fix issues when it is
9
+ loaded with different options that affect the layout.
@@ -0,0 +1,21 @@
1
+ = New Features
2
+
3
+ * A recheck_precompiled_assets plugin has been added, which allows
4
+ for checking for updates to the precompiled asset metadata file,
5
+ and automatically using the updated data.
6
+
7
+ * The common_logger plugin now supports a :method plugin option to
8
+ specify the method to call on the logger.
9
+
10
+ = Other Improvements
11
+
12
+ * Plugins and middleware that use keyword arguments are now supported
13
+ in Ruby 3.
14
+
15
+ * The compile_assets class method in the assets plugin now uses an
16
+ atomic approach to writing the precompiled asset metadata file.
17
+
18
+ * Minor method visibility issues have been fixed. The custom_matchers
19
+ plugin no longer makes the unsupported_matcher request method
20
+ public, and the render plugin no longer makes the _layout_method
21
+ public when the application is frozen.
data/lib/roda.rb CHANGED
@@ -114,6 +114,7 @@ class Roda
114
114
  alias_method meth, temp_method
115
115
  undef_method temp_method
116
116
  private meth
117
+ alias_method meth, meth
117
118
  meth = :"#{meth}_arity"
118
119
  elsif required_args > 1
119
120
  b = block
@@ -144,6 +145,7 @@ class Roda
144
145
 
145
146
  define_method(meth, &block)
146
147
  private meth
148
+ alias_method meth, meth
147
149
 
148
150
  if arity_meth
149
151
  required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(instance_method(meth))
@@ -167,6 +169,7 @@ class Roda
167
169
  send(meth, *a)
168
170
  end
169
171
  private arity_meth
172
+ alias_method arity_meth, arity_meth
170
173
  end
171
174
 
172
175
  call_meth
@@ -199,6 +202,7 @@ class Roda
199
202
 
200
203
  private
201
204
 
205
+ alias set_default_headers set_default_headers
202
206
  def set_default_headers
203
207
  @headers['Content-Type'] ||= 'text/html'
204
208
  end
@@ -288,6 +292,9 @@ class Roda
288
292
  plugin.configure(self, *args, &block) if plugin.respond_to?(:configure)
289
293
  @app = nil
290
294
  end
295
+ # :nocov:
296
+ ruby2_keywords(:plugin) if respond_to?(:ruby2_keywords, true)
297
+ # :nocov:
291
298
 
292
299
  # Setup routing tree for the current Roda application, and build the
293
300
  # underlying rack application using the stored middleware. Requires
@@ -323,6 +330,9 @@ class Roda
323
330
  @middleware << [args, block].freeze
324
331
  @app = nil
325
332
  end
333
+ # :nocov:
334
+ ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true)
335
+ # :nocov:
326
336
 
327
337
  private
328
338
 
@@ -403,6 +413,7 @@ class Roda
403
413
  class_eval("def _roda_before; #{meths.join(';')} end", __FILE__, __LINE__)
404
414
  end
405
415
  private :_roda_before
416
+ alias_method :_roda_before, :_roda_before
406
417
  end
407
418
  end
408
419
 
@@ -419,6 +430,7 @@ class Roda
419
430
  class_eval("def _roda_after(res); #{meths.map{|s| "#{s}(res)"}.join(';')} end", __FILE__, __LINE__)
420
431
  end
421
432
  private :_roda_after
433
+ alias_method :_roda_after, :_roda_after
422
434
  end
423
435
  end
424
436
 
data/lib/roda/cache.rb CHANGED
@@ -22,6 +22,13 @@ class Roda
22
22
  @mutex.synchronize{@hash[key] = value}
23
23
  end
24
24
 
25
+ # Return the frozen internal hash. The internal hash can then
26
+ # be accessed directly since it is frozen and there are no
27
+ # thread safety issues.
28
+ def freeze
29
+ @hash.freeze
30
+ end
31
+
25
32
  private
26
33
 
27
34
  # Create a copy of the cache with a separate mutex.
@@ -376,7 +376,7 @@ class Roda
376
376
 
377
377
  if opts[:precompiled] && !opts[:compiled] && ::File.exist?(opts[:precompiled])
378
378
  require 'json'
379
- opts[:compiled] = (app.opts[:json_parser] || ::JSON.method(:parse)).call(::File.read(opts[:precompiled]))
379
+ opts[:compiled] = app.send(:_precompiled_asset_metadata, opts[:precompiled])
380
380
  end
381
381
 
382
382
  if opts[:early_hints]
@@ -455,7 +455,7 @@ class Roda
455
455
  require 'fileutils'
456
456
 
457
457
  unless assets_opts[:compiled]
458
- opts[:assets] = assets_opts.merge(:compiled => {})
458
+ opts[:assets] = assets_opts.merge(:compiled => _compiled_assets_initial_hash).freeze
459
459
  end
460
460
 
461
461
  if type == nil
@@ -465,10 +465,12 @@ class Roda
465
465
  _compile_assets(type)
466
466
  end
467
467
 
468
- if assets_opts[:precompiled]
468
+ if precompile_file = assets_opts[:precompiled]
469
469
  require 'json'
470
- ::FileUtils.mkdir_p(File.dirname(assets_opts[:precompiled]))
471
- ::File.open(assets_opts[:precompiled], 'wb'){|f| f.write((opts[:json_serializer] || :to_json.to_proc).call(assets_opts[:compiled]))}
470
+ ::FileUtils.mkdir_p(File.dirname(precompile_file))
471
+ tmp_file = "#{precompile_file}.tmp"
472
+ ::File.open(tmp_file, 'wb'){|f| f.write((opts[:json_serializer] || :to_json.to_proc).call(assets_opts[:compiled]))}
473
+ ::File.rename(tmp_file, precompile_file)
472
474
  end
473
475
 
474
476
  assets_opts[:compiled]
@@ -476,6 +478,11 @@ class Roda
476
478
 
477
479
  private
478
480
 
481
+ # The initial hash to use to store compiled asset metadata.
482
+ def _compiled_assets_initial_hash
483
+ {}
484
+ end
485
+
479
486
  # Internals of compile_assets, handling recursive calls for loading
480
487
  # all asset groups under the given type.
481
488
  def _compile_assets(type)
@@ -493,6 +500,11 @@ class Roda
493
500
  end
494
501
  end
495
502
 
503
+ # The precompiled asset metadata stored in the given file
504
+ def _precompiled_asset_metadata(file)
505
+ (opts[:json_parser] || ::JSON.method(:parse)).call(::File.read(file))
506
+ end
507
+
496
508
  # Compile each array of files for the given type into a single
497
509
  # file. Dirs should be an array of asset group names, if these
498
510
  # are files in an asset group.
@@ -794,23 +806,32 @@ class Roda
794
806
  # handled.
795
807
  def assets_matchers
796
808
  @assets_matchers ||= [:css, :js].map do |t|
797
- [t, assets_regexp(t)].freeze if roda_class.assets_opts[t]
809
+ if regexp = assets_regexp(t)
810
+ [t, regexp].freeze
811
+ end
798
812
  end.compact.freeze
799
813
  end
800
814
 
801
815
  private
802
816
 
817
+ # A string for the asset filename for the asset type, key, and digest.
818
+ def _asset_regexp(type, key, digest)
819
+ "#{key.sub(/\A#{type}/, '')}.#{digest}.#{type}"
820
+ end
821
+
803
822
  # The regexp matcher to use for the given type. This handles any asset groups
804
823
  # for the asset types.
805
824
  def assets_regexp(type)
806
825
  o = roda_class.assets_opts
807
826
  if compiled = o[:compiled]
808
- assets = compiled.select{|k,_| k =~ /\A#{type}/}.map do |k, md|
809
- "#{k.sub(/\A#{type}/, '')}.#{md}.#{type}"
810
- end
827
+ assets = compiled.
828
+ select{|k,_| k =~ /\A#{type}/}.
829
+ map{|k, md| _asset_regexp(type, k, md)}
830
+ return if assets.empty?
811
831
  /#{o[:"compiled_#{type}_prefix"]}(#{Regexp.union(assets)})/
812
832
  else
813
- assets = unnest_assets_hash(o[type])
833
+ return unless assets = o[type]
834
+ assets = unnest_assets_hash(assets)
814
835
  ts = o[:timestamp_paths]
815
836
  /#{o[:"#{type}_prefix"]}#{"\\d+#{ts}" if ts}(#{Regexp.union(assets.uniq)})#{o[:"#{type}_suffix"]}/
816
837
  end
@@ -18,10 +18,11 @@ class Roda
18
18
  # plugin :common_logger
19
19
  # plugin :common_logger, $stdout
20
20
  # plugin :common_logger, Logger.new('filename')
21
+ # plugin :common_logger, Logger.new('filename'), method: :debug
21
22
  module CommonLogger
22
- def self.configure(app, logger=nil)
23
+ def self.configure(app, logger=nil, opts=OPTS)
23
24
  app.opts[:common_logger] = logger || app.opts[:common_logger] || $stderr
24
- app.opts[:common_logger_meth] = app.opts[:common_logger].method(logger.respond_to?(:write) ? :write : :<<)
25
+ app.opts[:common_logger_meth] = app.opts[:common_logger].method(opts.fetch(:method){logger.respond_to?(:write) ? :write : :<<})
25
26
  end
26
27
 
27
28
  if RUBY_VERSION >= '2.1'
@@ -68,6 +68,8 @@ class Roda
68
68
  end
69
69
 
70
70
  module RequestMethods
71
+ private
72
+
71
73
  # Try custom matchers before calling super
72
74
  def unsupported_matcher(matcher)
73
75
  roda_class.opts[:custom_matchers].each do |match_class, meth|
@@ -33,6 +33,7 @@ class Roda
33
33
  response_class.class_eval(<<-END, __FILE__, __LINE__+1)
34
34
  private
35
35
 
36
+ alias set_default_headers set_default_headers
36
37
  def set_default_headers
37
38
  h = @headers
38
39
  #{headers.map{|k,v| "h[#{k.inspect}] ||= #{v.inspect}"}.join('; ')}
@@ -54,6 +54,13 @@ class Roda
54
54
  :body=>lambda do |s, e|
55
55
  format = lambda{|h| h.map{|k, v| "#{k.inspect} => #{v.inspect}"}.sort.join("\n")}
56
56
 
57
+ begin
58
+ params = s.request.params
59
+ params = (format[params] unless params.empty?)
60
+ rescue
61
+ params = 'Invalid Parameters!'
62
+ end
63
+
57
64
  message = String.new
58
65
  message << <<END
59
66
  Path: #{s.request.path}
@@ -73,12 +80,12 @@ ENV:
73
80
  #{format[s.env]}
74
81
  END
75
82
 
76
- unless s.request.params.empty?
83
+ if params
77
84
  message << <<END
78
85
 
79
86
  Params:
80
87
 
81
- #{format[s.request.params]}
88
+ #{params}
82
89
  END
83
90
  end
84
91
 
@@ -71,6 +71,13 @@ class Roda
71
71
 
72
72
  format = lambda{|h| h.map{|k, v| "#{k.inspect} => #{v.inspect}"}.sort.join("\n")}
73
73
 
74
+ begin
75
+ params = request.params
76
+ params = (format[params] unless params.empty?)
77
+ rescue
78
+ params = 'Invalid Parameters!'
79
+ end
80
+
74
81
  message = String.new
75
82
  message << <<END
76
83
  Path: #{request.path}
@@ -91,12 +98,12 @@ ENV:
91
98
  #{format[env]}
92
99
  END
93
100
 
94
- unless request.params.empty?
101
+ if params
95
102
  message << <<END
96
103
 
97
104
  Params:
98
105
 
99
- #{format[request.params]}
106
+ #{params}
100
107
  END
101
108
  end
102
109
 
@@ -476,6 +476,8 @@ class Roda
476
476
 
477
477
  undef_method :match_rcpt
478
478
  undef_method :match_text
479
+ undef_method :match_body
480
+ undef_method :match_subject
479
481
 
480
482
  # Same as +header+, but also mark the message as being handled.
481
483
  def handle_header(key, value=nil)
@@ -13,32 +13,33 @@ class Roda
13
13
  # all of the child processes can use the same precompiled templates, which
14
14
  # saves memory.
15
15
  #
16
- # After loading the plugin, you can call +precompile_templates+ with
17
- # the pattern of templates you would like to precompile:
16
+ # Another advantage of the precompile_templates plugin is that after
17
+ # template precompilation, access to the template file in the file system is
18
+ # no longer needed, so this can be used with security features that do not
19
+ # allow access to the template files at runtime.
20
+ #
21
+ # After loading the plugin, you should call precompile_views with an array
22
+ # of views to compile, using the same argument you are passing to view or
23
+ # render:
18
24
  #
19
25
  # plugin :precompile_templates
20
- # precompile_templates "views/\*\*/*.erb"
26
+ # precompile_views %w'view1 view2'
27
+ #
28
+ # If the view requires local variables, you should call precompile_views with a second
29
+ # argument for the local variables:
21
30
  #
22
- # That will precompile all erb template files in the views directory or
23
- # any subdirectory.
31
+ # plugin :precompile_templates
32
+ # precompile_views :view3, [:local_var1, :local_var2]
24
33
  #
25
- # If the templates use local variables, you need to specify which local
26
- # variables to precompile, which should be an array of symbols:
34
+ # After all templates are precompiled, you can optionally use freeze_template_caches!,
35
+ # which will freeze the template caches so that any template compilation at runtime
36
+ # will result in an error. This also speeds up template cache access, since the
37
+ # template caches no longer need a mutex.
27
38
  #
28
- # precompile_templates 'views/users/_*.erb', locals: [:user]
39
+ # freeze_template_caches!
29
40
  #
30
41
  # Note that you should use Tilt 2.0.1+ if you are using this plugin, so
31
42
  # that locals are handled in the same order.
32
- #
33
- # You can specify other render options when calling +precompile_templates+,
34
- # including +:cache_key+, +:template_class+, and +:template_opts+. If you
35
- # are passing any of those options to render/view for the template, you
36
- # should pass the same options when precompiling the template.
37
- #
38
- # To compile inline templates, just pass a single hash containing an :inline
39
- # to +precompile_templates+:
40
- #
41
- # precompile_templates inline: some_template_string
42
43
  module PrecompileTemplates
43
44
  # Load the render plugin as precompile_templates depends on it.
44
45
  def self.load_dependencies(app, opts=OPTS)
@@ -46,8 +47,49 @@ class Roda
46
47
  end
47
48
 
48
49
  module ClassMethods
49
- # Precompile the templates using the given options. See PrecompileTemplates
50
- # for details.
50
+ # Freeze the template caches. Should be called after precompiling all templates during
51
+ # application startup, if you don't want to allow templates to be cached at runtime.
52
+ # In addition to ensuring that no templates are compiled at runtime, this also speeds
53
+ # up rendering by freezing the template caches, so that a mutex is not needed to access
54
+ # them.
55
+ def freeze_template_caches!
56
+ _freeze_layout_method
57
+
58
+ opts[:render] = render_opts.merge(
59
+ :cache=>render_opts[:cache].freeze,
60
+ :template_method_cache=>render_opts[:template_method_cache].freeze,
61
+ ).freeze
62
+ self::RodaCompiledTemplates.freeze
63
+
64
+ nil
65
+ end
66
+
67
+ # Precompile the templates using the given options. Note that this doesn't
68
+ # handle optimized template methods supported in newer versions of Roda, but
69
+ # there are still cases where makes sense to use it.
70
+ #
71
+ # You can call +precompile_templates+ with the pattern of templates you would
72
+ # like to precompile:
73
+ #
74
+ # precompile_templates "views/**/*.erb"
75
+ #
76
+ # That will precompile all erb template files in the views directory or
77
+ # any subdirectory.
78
+ #
79
+ # If the templates use local variables, you need to specify which local
80
+ # variables to precompile, which should be an array of symbols:
81
+ #
82
+ # precompile_templates 'views/users/_*.erb', locals: [:user]
83
+ #
84
+ # You can specify other render options when calling +precompile_templates+,
85
+ # including +:cache_key+, +:template_class+, and +:template_opts+. If you
86
+ # are passing any of those options to render/view for the template, you
87
+ # should pass the same options when precompiling the template.
88
+ #
89
+ # To compile inline templates, just pass a single hash containing an :inline
90
+ # to +precompile_templates+:
91
+ #
92
+ # precompile_templates inline: some_template_string
51
93
  def precompile_templates(pattern, opts=OPTS)
52
94
  if pattern.is_a?(Hash)
53
95
  opts = pattern.merge(opts)
@@ -68,7 +110,40 @@ class Roda
68
110
  instance = allocate
69
111
  compile_opts.each do |compile_opt|
70
112
  template = instance.send(:retrieve_template, compile_opt)
71
- Render.tilt_template_compiled_method(template, locals, self)
113
+ begin
114
+ Render.tilt_template_compiled_method(template, locals, self)
115
+ rescue NotImplementedError
116
+ # When freezing template caches, you may want to precompile a template for a
117
+ # template type that doesn't support template precompilation, just to populate
118
+ # the cache. Tilt rescues NotImplementedError in this case, which we can ignore.
119
+ nil
120
+ end
121
+ end
122
+
123
+ nil
124
+ end
125
+
126
+ # Precompile the given views with the given locals, handling optimized template methods.
127
+ def precompile_views(views, locals=EMPTY_ARRAY)
128
+ instance = allocate
129
+ views = Array(views)
130
+
131
+ if locals.empty?
132
+ opts = OPTS
133
+ else
134
+ locals_hash = {}
135
+ locals.each{|k| locals_hash[k] = nil}
136
+ opts = {:locals=>locals_hash}
137
+ end
138
+
139
+ views.each do |view|
140
+ instance.send(:retrieve_template, instance.send(:render_template_opts, view, opts))
141
+ end
142
+
143
+ if locals_hash
144
+ views.each do |view|
145
+ instance.send(:_optimized_render_method_for_locals, view, locals_hash)
146
+ end
72
147
  end
73
148
 
74
149
  nil
@@ -0,0 +1,107 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The recheck_precompiled_assets plugin enables checking for the precompiled asset metadata file.
7
+ # You need to have already loaded the assets plugin with the +:precompiled+ option and the file
8
+ # specified by the +:precompiled+ option must already exist in order to use the
9
+ # recheck_precompiled_assets plugin.
10
+ #
11
+ # Any time you want to check whether the precompiled asset metadata file has changed and should be
12
+ # reloaded, you can call the +recheck_precompiled_assets+ class method. This method will check
13
+ # whether the file has changed, and reload it if it has. If you want to check for modifications on
14
+ # every request, you can use +self.class.recheck_precompiled_assets+ inside your route block.
15
+ module RecheckPrecompiledAssets
16
+ # Thread safe wrapper for the compiled asset metadata hash. Does not wrap all
17
+ # hash methods, only a few that are used.
18
+ class CompiledAssetsHash
19
+ include Enumerable
20
+
21
+ def initialize
22
+ @hash = {}
23
+ @mutex = Mutex.new
24
+ end
25
+
26
+ def [](key)
27
+ @mutex.synchronize{@hash[key]}
28
+ end
29
+
30
+ def []=(key, value)
31
+ @mutex.synchronize{@hash[key] = value}
32
+ end
33
+
34
+ def replace(hash)
35
+ hash = hash.instance_variable_get(:@hash) if (CompiledAssetsHash === hash)
36
+ @mutex.synchronize{@hash.replace(hash)}
37
+ self
38
+ end
39
+
40
+ def each(&block)
41
+ @mutex.synchronize{@hash.dup}.each(&block)
42
+ self
43
+ end
44
+
45
+ def to_json(*args)
46
+ @mutex.synchronize{@hash.dup}.to_json(*args)
47
+ end
48
+ end
49
+
50
+ def self.load_dependencies(app)
51
+ unless app.respond_to?(:assets_opts) && app.assets_opts[:precompiled]
52
+ raise RodaError, "must load assets plugin with precompiled option before loading recheck_precompiled_assets plugin"
53
+ end
54
+ end
55
+
56
+ def self.configure(app)
57
+ precompiled_file = app.assets_opts[:precompiled]
58
+ prev_mtime = ::File.mtime(precompiled_file)
59
+ app.instance_exec do
60
+ opts[:assets] = opts[:assets].merge(:compiled=>_compiled_assets_initial_hash.replace(assets_opts[:compiled])).freeze
61
+
62
+ define_singleton_method(:recheck_precompiled_assets) do
63
+ new_mtime = ::File.mtime(precompiled_file)
64
+ if new_mtime != prev_mtime
65
+ prev_mtime = new_mtime
66
+ assets_opts[:compiled].replace(_precompiled_asset_metadata(precompiled_file))
67
+
68
+ # Unset the cached asset matchers, so new ones will be generated.
69
+ # This is needed in case the new precompiled metadata uses
70
+ # different files.
71
+ app::RodaRequest.instance_variable_set(:@assets_matchers, nil)
72
+ end
73
+ end
74
+ singleton_class.send(:alias_method, :recheck_precompiled_assets, :recheck_precompiled_assets)
75
+ end
76
+ end
77
+
78
+ module ClassMethods
79
+ private
80
+
81
+ # Wrap the precompiled asset metadata in a thread-safe hash.
82
+ def _precompiled_asset_metadata(file)
83
+ CompiledAssetsHash.new.replace(super)
84
+ end
85
+
86
+ # Use a thread-safe wrapper of a hash for the :compiled assets option, since
87
+ # the recheck_precompiled_asset_metadata can modify it at runtime.
88
+ def _compiled_assets_initial_hash
89
+ CompiledAssetsHash.new
90
+ end
91
+ end
92
+
93
+ module RequestClassMethods
94
+ private
95
+
96
+ # Use a regexp that matches any digest. When the precompiled asset metadata
97
+ # file is updated, this allows requests for a previous precompiled asset to
98
+ # still work.
99
+ def _asset_regexp(type, key, _)
100
+ /#{Regexp.escape(key.sub(/\A#{type}/, ''))}\.[0-9a-fA-F]+\.#{type}/
101
+ end
102
+ end
103
+ end
104
+
105
+ register_plugin(:recheck_precompiled_assets, RecheckPrecompiledAssets)
106
+ end
107
+ end
@@ -41,6 +41,7 @@ class Roda
41
41
  # Return a relative prefix to append to an absolute path to a relative path
42
42
  # based on the current path of the request.
43
43
  def relative_prefix
44
+ return @_relative_prefix if @_relative_prefix
44
45
  env = @_request.env
45
46
  script_name = env["SCRIPT_NAME"]
46
47
  path_info = env["PATH_INFO"]
@@ -50,16 +51,16 @@ class Roda
50
51
  case script_name.getbyte(0)
51
52
  when nil # SCRIPT_NAME empty
52
53
  unless path_info.getbyte(0) == 47 # PATH_INFO starts with /
53
- return ''
54
+ return(@_relative_prefix = '')
54
55
  end
55
56
  when 47 # SCRIPT_NAME starts with /
56
57
  # nothing
57
58
  else
58
- return ''
59
+ return(@_relative_prefix = '')
59
60
  end
60
61
 
61
62
  slash_count = script_name.count('/') + path_info.count('/')
62
- if slash_count > 1
63
+ @_relative_prefix = if slash_count > 1
63
64
  ("../" * (slash_count - 2)) << ".."
64
65
  else
65
66
  "."
@@ -106,7 +106,7 @@ class Roda
106
106
  # :template_block :: Pass this block when creating the underlying template,
107
107
  # ignored when using :inline. Disables caching of the
108
108
  # template by default.
109
- # :template_class :: Provides the template class to use, inside of using
109
+ # :template_class :: Provides the template class to use, instead of using
110
110
  # Tilt or <tt>Tilt[:engine]</tt>.
111
111
  #
112
112
  # Here's an example of using these options:
@@ -183,6 +183,7 @@ class Roda
183
183
  app.const_set(:RodaCompiledTemplates, compiled_templates_module)
184
184
  end
185
185
  opts[:template_method_cache] = orig_method_cache || (opts[:cache_class] || RodaCache).new
186
+ opts[:template_method_cache][:_roda_layout] = nil if opts[:template_method_cache][:_roda_layout]
186
187
  opts[:cache] = orig_cache || (opts[:cache_class] || RodaCache).new
187
188
 
188
189
  opts[:layout_opts] = (opts[:layout_opts] || {}).dup
@@ -333,6 +334,25 @@ class Roda
333
334
  end
334
335
 
335
336
  module ClassMethods
337
+ # :nocov:
338
+ if COMPILED_METHOD_SUPPORT
339
+ # :nocov:
340
+ # If using compiled methods and there is an optimized layout, speed up
341
+ # access to the layout method to improve the performance of view.
342
+ def freeze
343
+ begin
344
+ _freeze_layout_method
345
+ rescue
346
+ # This is only for optimization, if any errors occur, they can be ignored.
347
+ # One possibility for error is the app doesn't use a layout, but doesn't
348
+ # specifically set the :layout=>false plugin option.
349
+ nil
350
+ end
351
+
352
+ super
353
+ end
354
+ end
355
+
336
356
  # Copy the rendering options into the subclass, duping
337
357
  # them as necessary to prevent changes in the subclass
338
358
  # affecting the parent class.
@@ -352,6 +372,29 @@ class Roda
352
372
  def render_opts
353
373
  opts[:render]
354
374
  end
375
+
376
+ private
377
+
378
+ # Precompile the layout method, to reduce method calls to look it up at runtime.
379
+ def _freeze_layout_method
380
+ if render_opts[:layout]
381
+ instance = allocate
382
+ instance.send(:retrieve_template, instance.send(:view_layout_opts, OPTS))
383
+
384
+ # :nocov:
385
+ if COMPILED_METHOD_SUPPORT
386
+ # :nocov:
387
+ if (layout_template = render_opts[:optimize_layout]) && !opts[:render][:optimized_layout_method_created]
388
+ instance.send(:retrieve_template, :template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout)
389
+ layout_method = opts[:render][:template_method_cache][:_roda_layout]
390
+ define_method(:_layout_method){layout_method}
391
+ private :_layout_method
392
+ alias_method(:_layout_method, :_layout_method)
393
+ opts[:render] = opts[:render].merge(:optimized_layout_method_created=>true)
394
+ end
395
+ end
396
+ end
397
+ end
355
398
  end
356
399
 
357
400
  module InstanceMethods
@@ -375,19 +418,21 @@ class Roda
375
418
  # Render the given template. If there is a default layout
376
419
  # for the class, take the result of the template rendering
377
420
  # and render it inside the layout. See Render for details.
378
- def view(template, opts = (optimized_template = _cached_template_method(template); OPTS))
379
- if optimized_template
380
- content = send(optimized_template, OPTS)
381
-
382
- render_opts = self.class.opts[:render]
383
- if layout_template = render_opts[:optimize_layout]
384
- method_cache = render_opts[:template_method_cache]
385
- unless layout_method = method_cache[:_roda_layout]
386
- retrieve_template(:template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout)
387
- layout_method = method_cache[:_roda_layout]
388
- end
421
+ def view(template, opts = (content = _optimized_view_content(template); OPTS))
422
+ if content
423
+ # First, check if the optimized layout method has already been created,
424
+ # and use it if so. This way avoids the extra conditional and local variable
425
+ # assignments in the next section.
426
+ if layout_method = _layout_method
427
+ return send(layout_method, OPTS){content}
428
+ end
389
429
 
390
- if layout_method
430
+ # If we have an optimized template method but no optimized layout method, create the
431
+ # optimized layout method if possible and use it. If you can't create the optimized
432
+ # layout method, fall through to the slower approach.
433
+ if layout_template = self.class.opts[:render][:optimize_layout]
434
+ retrieve_template(:template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout)
435
+ if layout_method = _layout_method
391
436
  return send(layout_method, OPTS){content}
392
437
  end
393
438
  end
@@ -428,6 +473,11 @@ class Roda
428
473
  method_cache[template]
429
474
  end
430
475
 
476
+ # Return a symbol containing the optimized layout method
477
+ def _layout_method
478
+ self.class.opts[:render][:template_method_cache][:_roda_layout]
479
+ end
480
+
431
481
  # Use an optimized render path for templates with a hash of locals. Returns the result
432
482
  # of the template render if the optimized path is used, or nil if the optimized
433
483
  # path is not used and the long method needs to be used.
@@ -469,19 +519,37 @@ class Roda
469
519
  end
470
520
  end
471
521
  end
522
+
523
+ # Get the content for #view, or return nil to use the unoptimized approach. Only called if
524
+ # a single argument is passed to view.
525
+ def _optimized_view_content(template)
526
+ if optimized_template = _cached_template_method(template)
527
+ send(optimized_template, OPTS)
528
+ elsif template.is_a?(Hash) && template.length == 1
529
+ template[:content]
530
+ end
531
+ end
472
532
  else
473
533
  # :nocov:
474
- def _cached_template_method(template)
534
+ def _cached_template_method(_)
475
535
  nil
476
536
  end
477
537
 
478
- def _cached_template_method_key(template)
538
+ def _cached_template_method_key(_)
539
+ nil
540
+ end
541
+
542
+ def _layout_method
479
543
  nil
480
544
  end
481
545
 
482
546
  def _optimized_render_method_for_locals(_, _)
483
547
  nil
484
548
  end
549
+
550
+ def _optimized_view_content(template)
551
+ nil
552
+ end
485
553
  # :nocov:
486
554
  end
487
555
 
@@ -51,6 +51,10 @@ class Roda
51
51
  def _cached_template_method(template)
52
52
  nil
53
53
  end
54
+
55
+ def _optimized_view_content(template)
56
+ nil
57
+ end
54
58
  end
55
59
 
56
60
  def render_locals
@@ -141,6 +141,7 @@ class Roda
141
141
  app::RodaRequest.send(:define_method, type) do |&block|
142
142
  on_type(type, &block)
143
143
  end
144
+ app::RodaRequest.send(:alias_method, type, type)
144
145
  end
145
146
 
146
147
  app.opts[:type_routing] = config.freeze
@@ -229,7 +229,7 @@ class Roda
229
229
  #
230
230
  # Note that if there are multiple conversion Error raised inside a +convert!+ or +convert_each!+
231
231
  # block, they are recorded and a single TypecastParams::Error instance is raised after
232
- # processing the block. TypecastParams::Error#params_names can be called on the exception to
232
+ # processing the block. TypecastParams::Error#param_names can be called on the exception to
233
233
  # get an array of all parameter names with conversion issues, and TypecastParams::Error#all_errors
234
234
  # can be used to get an array of all Error instances.
235
235
  #
@@ -724,7 +724,7 @@ class Roda
724
724
  raise Error, "parameter #{param_name(nil)} is not a hash" if do_raise
725
725
  return
726
726
  end
727
- present = @obj.has_key?(key)
727
+ present = !@obj[key].nil?
728
728
  when Integer
729
729
  unless @obj.is_a?(Array)
730
730
  raise Error, "parameter #{param_name(nil)} is not an array" if do_raise
data/lib/roda/version.rb CHANGED
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 3
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 37
7
+ RodaMinorVersion = 42
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.37.0
4
+ version: 3.42.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-16 00:00:00.000000000 Z
11
+ date: 2021-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -174,16 +174,8 @@ extra_rdoc_files:
174
174
  - MIT-LICENSE
175
175
  - CHANGELOG
176
176
  - doc/conventions.rdoc
177
- - doc/release_notes/3.7.0.txt
178
177
  - doc/release_notes/3.0.0.txt
179
178
  - doc/release_notes/3.1.0.txt
180
- - doc/release_notes/3.2.0.txt
181
- - doc/release_notes/3.3.0.txt
182
- - doc/release_notes/3.4.0.txt
183
- - doc/release_notes/3.5.0.txt
184
- - doc/release_notes/3.6.0.txt
185
- - doc/release_notes/3.8.0.txt
186
- - doc/release_notes/3.9.0.txt
187
179
  - doc/release_notes/3.10.0.txt
188
180
  - doc/release_notes/3.11.0.txt
189
181
  - doc/release_notes/3.12.0.txt
@@ -195,6 +187,7 @@ extra_rdoc_files:
195
187
  - doc/release_notes/3.17.0.txt
196
188
  - doc/release_notes/3.18.0.txt
197
189
  - doc/release_notes/3.19.0.txt
190
+ - doc/release_notes/3.2.0.txt
198
191
  - doc/release_notes/3.20.0.txt
199
192
  - doc/release_notes/3.21.0.txt
200
193
  - doc/release_notes/3.22.0.txt
@@ -205,6 +198,7 @@ extra_rdoc_files:
205
198
  - doc/release_notes/3.27.0.txt
206
199
  - doc/release_notes/3.28.0.txt
207
200
  - doc/release_notes/3.29.0.txt
201
+ - doc/release_notes/3.3.0.txt
208
202
  - doc/release_notes/3.30.0.txt
209
203
  - doc/release_notes/3.31.0.txt
210
204
  - doc/release_notes/3.32.0.txt
@@ -213,6 +207,17 @@ extra_rdoc_files:
213
207
  - doc/release_notes/3.35.0.txt
214
208
  - doc/release_notes/3.36.0.txt
215
209
  - doc/release_notes/3.37.0.txt
210
+ - doc/release_notes/3.38.0.txt
211
+ - doc/release_notes/3.39.0.txt
212
+ - doc/release_notes/3.4.0.txt
213
+ - doc/release_notes/3.40.0.txt
214
+ - doc/release_notes/3.41.0.txt
215
+ - doc/release_notes/3.42.0.txt
216
+ - doc/release_notes/3.5.0.txt
217
+ - doc/release_notes/3.6.0.txt
218
+ - doc/release_notes/3.7.0.txt
219
+ - doc/release_notes/3.8.0.txt
220
+ - doc/release_notes/3.9.0.txt
216
221
  files:
217
222
  - CHANGELOG
218
223
  - MIT-LICENSE
@@ -251,7 +256,12 @@ files:
251
256
  - doc/release_notes/3.35.0.txt
252
257
  - doc/release_notes/3.36.0.txt
253
258
  - doc/release_notes/3.37.0.txt
259
+ - doc/release_notes/3.38.0.txt
260
+ - doc/release_notes/3.39.0.txt
254
261
  - doc/release_notes/3.4.0.txt
262
+ - doc/release_notes/3.40.0.txt
263
+ - doc/release_notes/3.41.0.txt
264
+ - doc/release_notes/3.42.0.txt
255
265
  - doc/release_notes/3.5.0.txt
256
266
  - doc/release_notes/3.6.0.txt
257
267
  - doc/release_notes/3.7.0.txt
@@ -333,6 +343,7 @@ files:
333
343
  - lib/roda/plugins/precompile_templates.rb
334
344
  - lib/roda/plugins/public.rb
335
345
  - lib/roda/plugins/r.rb
346
+ - lib/roda/plugins/recheck_precompiled_assets.rb
336
347
  - lib/roda/plugins/relative_path.rb
337
348
  - lib/roda/plugins/render.rb
338
349
  - lib/roda/plugins/render_each.rb
@@ -390,7 +401,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
390
401
  - !ruby/object:Gem::Version
391
402
  version: '0'
392
403
  requirements: []
393
- rubygems_version: 3.1.4
404
+ rubygems_version: 3.2.3
394
405
  signing_key:
395
406
  specification_version: 4
396
407
  summary: Routing tree web toolkit