roda 3.35.0 → 3.40.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 370e18bb1d1cfb9fb8b0c10fda1a4f57161a39c6d733b628ddd60058d231f896
4
- data.tar.gz: fe0c8ddb499cdbafb0f047d9445fca726f742e38674f90731f3421132c373365
3
+ metadata.gz: 8e87645db18482f3ad97d106e268d76b2567d074490f37d7e99d52d06e4e5483
4
+ data.tar.gz: b84b082c2f27fcbd31254cb80de5e6610b7417e722eaf3dc3d8753a4604a0701
5
5
  SHA512:
6
- metadata.gz: 60db0b47d97ae40437e0396bc795ac360194bd88067e6611cf277888d054e3c41786eb062f4ab31d6683cb4755a3a27d148a576236c77a4d46db67fc2accc7e1
7
- data.tar.gz: 44745afaa29ccee2d51a132f7b7be4da6818191814a9ff8a2afd52f34636789cb0e4d22eb63ade49ea0411470a73b05a1996c7bb70cc7edf04264fb2fddadb83
6
+ metadata.gz: 3b21d6fa4b33c029a8b23a8ec437e70e2799e3f2bec27239d3cbfc5a1a4bb8cd82fc95309ad27d22243695298535b02687b0948218df1f7a1a0812242079e3b7
7
+ data.tar.gz: 3dc9629b16a4d9f496cee61bc40396f6893bfe114666ba7554327cd052946719410e2f4e89a27388cea291fd7b5ba1bcd37c669aa1dfc7b64c48a6622bafb013
data/CHANGELOG CHANGED
@@ -1,3 +1,39 @@
1
+ = 3.40.0 (2021-01-14)
2
+
3
+ * Add freeze_template_caches! to the precompile_templates plugin, which ensures all templates are precompiled, and speeds up template access (jeremyevans)
4
+
5
+ * Add precompile_views to the precompile_templates plugin, which precompiles the optimized render methods (jeremyevans)
6
+
7
+ * Have RodaCache#freeze return the frozen internal hash (which no longer needs a mutex for thread-safety) (jeremyevans)
8
+
9
+ * Speed up the view method in the render plugin even more when freezing the application (jeremyevans)
10
+
11
+ * Speed up the view method in the render plugin when called with a single argument (jeremyevans)
12
+
13
+ = 3.39.0 (2020-12-15)
14
+
15
+ * Speed up relative_path plugin if relative_path or relative_prefix is called more than once (jeremyevans)
16
+
17
+ * Avoid method redefinition warnings in verbose warning mode (jeremyevans)
18
+
19
+ * Make typecast_params.convert! handle explicit nil values the same as missing values (jeremyevans)
20
+
21
+ = 3.38.0 (2020-11-16)
22
+
23
+ * Make error_email and error_mail plugins rescue invalid parameter errors when preparing the email body (jeremyevans)
24
+
25
+ = 3.37.0 (2020-10-16)
26
+
27
+ * Add custom_matchers plugin, for supporting arbitrary objects as matchers (jeremyevans)
28
+
29
+ = 3.36.0 (2020-09-14)
30
+
31
+ * Add multi_public plugin, for serving files from multiple public directories (jeremyevans)
32
+
33
+ * Support report-to directive in the content_security_policy plugin (jeremyevans)
34
+
35
+ * Add Vary response header when using type_routing plugin with Accept request header to prevent caching issues (jeremyevans)
36
+
1
37
  = 3.35.0 (2020-08-14)
2
38
 
3
39
  * Add r plugin for r method for accessing request, useful when r local variable is not in scope (jeremyevans)
@@ -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,17 @@
1
+ = New Features
2
+
3
+ * A multi_public plugin has been added, which allows serving static
4
+ files from multiple separate directories. This is especially
5
+ useful when there are different access control requirements per
6
+ directory.
7
+
8
+ * The content_security_policy now supports a
9
+ content_security_policy.report_to method to set the
10
+ report-to directive.
11
+
12
+ = Other Improvements
13
+
14
+ * When using the type_routing plugin and performing type routing
15
+ using the Accept request header, the Vary response header will be
16
+ added or updated so that http caches do not cache a response for one
17
+ type and serve it for a different type.
@@ -0,0 +1,42 @@
1
+ = New Features
2
+
3
+ * A custom_matchers plugin has been added, which allows using
4
+ arbitrary objects as matchers, as long as the matcher has been
5
+ registered. You can register matchers using the custom_matcher
6
+ class method, which takes the class of the matcher, and a block
7
+ which is yielded the matcher object. The block should return
8
+ nil or false if the matcher doesn't match, and any other value
9
+ if the matcher does match. Example:
10
+
11
+ plugin :custom_matchers
12
+ method_segment = Struct.new(:request_method, :next_segment)
13
+ custom_matcher(method_segment) do |matcher|
14
+ # self is the request instance ("r" yielded in the route block below)
15
+ if matcher.request_method == self.request_method
16
+ match(matcher.next_segment)
17
+ end
18
+ end
19
+
20
+ get_foo = method_segment.new('GET', 'foo')
21
+ post_any = method_segment.new('POST', String)
22
+ route do |r|
23
+ r.on('baz') do
24
+ r.on(get_foo) do
25
+ # GET method, /baz/foo prefix
26
+ end
27
+
28
+ r.is(post_any) do |seg|
29
+ # for POST /baz/bar, seg is "bar"
30
+ end
31
+ end
32
+
33
+ r.on('quux') do
34
+ r.is(get_foo) do
35
+ # GET method, /quux/foo route
36
+ end
37
+
38
+ r.on(post_any) do |seg|
39
+ # for POST /quux/xyz, seg is "xyz"
40
+ end
41
+ end
42
+ end
@@ -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.
@@ -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
@@ -403,6 +407,7 @@ class Roda
403
407
  class_eval("def _roda_before; #{meths.join(';')} end", __FILE__, __LINE__)
404
408
  end
405
409
  private :_roda_before
410
+ alias_method :_roda_before, :_roda_before
406
411
  end
407
412
  end
408
413
 
@@ -419,6 +424,7 @@ class Roda
419
424
  class_eval("def _roda_after(res); #{meths.map{|s| "#{s}(res)"}.join(';')} end", __FILE__, __LINE__)
420
425
  end
421
426
  private :_roda_after
427
+ alias_method :_roda_after, :_roda_after
422
428
  end
423
429
  end
424
430
 
@@ -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.
@@ -56,6 +56,7 @@ class Roda
56
56
  # * media_src
57
57
  # * object_src
58
58
  # * plugin_types
59
+ # * report_to
59
60
  # * report_uri
60
61
  # * require_sri_for
61
62
  # * sandbox
@@ -123,6 +124,7 @@ class Roda
123
124
  media-src
124
125
  object-src
125
126
  plugin-types
127
+ report-to
126
128
  report-uri
127
129
  require-sri-for
128
130
  sandbox
@@ -145,9 +147,7 @@ class Roda
145
147
  # add_* method name adds to the setting value, or clears setting if no values
146
148
  # are given.
147
149
  define_method("add_#{meth}") do |*args|
148
- if args.empty?
149
- @opts[setting]
150
- else
150
+ unless args.empty?
151
151
  @opts[setting] ||= EMPTY_ARRAY
152
152
  @opts[setting] += args
153
153
  @opts[setting].freeze
@@ -0,0 +1,87 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The custom_matchers plugin supports using arbitrary objects
7
+ # as matchers, as long as the application has been configured
8
+ # to accept such objects.
9
+ #
10
+ # After loading the plugin, support for custom matchers can be
11
+ # configured using the +custom_matcher+ class method. This
12
+ # method is generally passed the class of the object you want
13
+ # to use as a custom matcher, as well as a block. The block
14
+ # will be called in the context of the request instance
15
+ # with the specific matcher used in the match method.
16
+ #
17
+ # Blocks can append to the captures in order to yield the appropriate
18
+ # values to match blocks, or call request methods that append to the
19
+ # captures.
20
+ #
21
+ # Example:
22
+ #
23
+ # plugin :custom_matchers
24
+ # method_segment = Struct.new(:request_method, :next_segment)
25
+ # custom_matcher(method_segment) do |matcher|
26
+ # # self is the request instance ("r" yielded in the route block below)
27
+ # if matcher.request_method == self.request_method
28
+ # match(matcher.next_segment)
29
+ # end
30
+ # end
31
+ #
32
+ # get_foo = method_segment.new('GET', 'foo')
33
+ # post_any = method_segment.new('POST', String)
34
+ # route do |r|
35
+ # r.on('baz') do
36
+ # r.on(get_foo) do
37
+ # # GET method, /baz/foo prefix
38
+ # end
39
+ #
40
+ # r.is(post_any) do |seg|
41
+ # # for POST /baz/bar, seg is "bar"
42
+ # end
43
+ # end
44
+ #
45
+ # r.on('quux') do
46
+ # r.is(get_foo) do
47
+ # # GET method, /quux/foo route
48
+ # end
49
+ #
50
+ # r.on(post_any) do |seg|
51
+ # # for POST /quux/xyz, seg is "xyz"
52
+ # end
53
+ # end
54
+ # end
55
+ module CustomMatchers
56
+ def self.configure(app)
57
+ app.opts[:custom_matchers] ||= OPTS
58
+ end
59
+
60
+ module ClassMethods
61
+ def custom_matcher(match_class, &block)
62
+ custom_matchers = Hash[opts[:custom_matchers]]
63
+ meth = custom_matchers[match_class] = custom_matchers[match_class] || :"_custom_matcher_#{match_class}"
64
+ opts[:custom_matchers] = custom_matchers.freeze
65
+ self::RodaRequest.send(:define_method, meth, &block)
66
+ nil
67
+ end
68
+ end
69
+
70
+ module RequestMethods
71
+ # Try custom matchers before calling super
72
+ def unsupported_matcher(matcher)
73
+ roda_class.opts[:custom_matchers].each do |match_class, meth|
74
+ if match_class === matcher
75
+ return send(meth, matcher)
76
+ end
77
+ end
78
+
79
+ super
80
+ end
81
+ end
82
+ end
83
+
84
+ register_plugin(:custom_matchers, CustomMatchers)
85
+ end
86
+ end
87
+
@@ -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)
@@ -0,0 +1,87 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The multi_public plugin adds an +r.multi_public+ method that accepts an argument specifying
7
+ # a directory from which to serve static files. It is similar to the public plugin, but
8
+ # allows for multiple separate directories.
9
+ #
10
+ # Here's an example of using the multi_public plugin to serve 3 different types of files
11
+ # from 3 different directories:
12
+ #
13
+ # plugin :multi_public,
14
+ # img: 'static/images',
15
+ # font: 'assets/fonts',
16
+ # form: 'static/forms/pdfs'
17
+ #
18
+ # r.route do
19
+ # r.on "images" do
20
+ # r.multi_public(:img)
21
+ # end
22
+ #
23
+ # r.on "fonts" do
24
+ # r.multi_public(:font)
25
+ # end
26
+ #
27
+ # r.on "forms" do
28
+ # r.multi_public(:form)
29
+ # end
30
+ # end
31
+ #
32
+ # It is possible to simplify the routing tree for this using string keys and an array
33
+ # matcher:
34
+ #
35
+ # plugin :multi_public,
36
+ # 'images' => 'static/images',
37
+ # 'fonts' => 'assets/fonts',
38
+ # 'forms' => 'static/forms/pdfs'
39
+ #
40
+ # r.route do
41
+ # r.on %w"images fonts forms" do |dir|
42
+ # r.multi_public(dir)
43
+ # end
44
+ # end
45
+ #
46
+ # You can provide custom headers and default mime type for each directory using an array
47
+ # of three elements as the value, with the first element being the path, the second
48
+ # being the custom headers, and the third being the default mime type:
49
+ #
50
+ # plugin :multi_public,
51
+ # 'images' => ['static/images', {'Cache-Control'=>'max-age=86400'}, nil],
52
+ # 'fonts' => ['assets/fonts', {'Cache-Control'=>'max-age=31536000'}, 'font/ttf'],
53
+ # 'forms' => ['static/forms/pdfs', nil, 'application/pdf']
54
+ #
55
+ # r.route do
56
+ # r.on %w"images fonts forms" do |dir|
57
+ # r.multi_public(dir)
58
+ # end
59
+ # end
60
+ module MultiPublic
61
+ def self.load_dependencies(app, _, opts=OPTS)
62
+ app.plugin(:public, opts)
63
+ end
64
+
65
+ # Use the given directories to setup servers. Any opts are passed to the public plugin.
66
+ def self.configure(app, directories, _=OPTS)
67
+ roots = app.opts[:multi_public_servers] = (app.opts[:multi_public_servers] || {}).dup
68
+ directories.each do |key, path|
69
+ path, headers, mime = path
70
+ roots[key] = ::Rack::File.new(app.expand_path(path), headers||{}, mime||'text/plain')
71
+ end
72
+ roots.freeze
73
+ end
74
+
75
+ module RequestMethods
76
+ # Serve files from the directory corresponding to the given key if the file exists and
77
+ # this is a GET request.
78
+ def multi_public(key)
79
+ public_serve_with(roda_class.opts[:multi_public_servers].fetch(key))
80
+ end
81
+ end
82
+ end
83
+
84
+ register_plugin(:multi_public, MultiPublic)
85
+ end
86
+ end
87
+
@@ -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
@@ -60,21 +60,7 @@ class Roda
60
60
  module RequestMethods
61
61
  # Serve files from the public directory if the file exists and this is a GET request.
62
62
  def public
63
- if is_get?
64
- path = PARSER.unescape(real_remaining_path)
65
- return if path.include?("\0")
66
-
67
- roda_opts = roda_class.opts
68
- server = roda_opts[:public_server]
69
- path = ::File.join(server.root, *public_path_segments(path))
70
-
71
- public_serve_compressed(server, path, '.br', 'br') if roda_opts[:public_brotli]
72
- public_serve_compressed(server, path, '.gz', 'gzip') if roda_opts[:public_gzip]
73
-
74
- if public_file_readable?(path)
75
- halt public_serve(server, path)
76
- end
77
- end
63
+ public_serve_with(roda_class.opts[:public_server])
78
64
  end
79
65
 
80
66
  private
@@ -101,6 +87,22 @@ class Roda
101
87
  # :nocov:
102
88
  end
103
89
 
90
+ def public_serve_with(server)
91
+ return unless is_get?
92
+ path = PARSER.unescape(real_remaining_path)
93
+ return if path.include?("\0")
94
+
95
+ roda_opts = roda_class.opts
96
+ path = ::File.join(server.root, *public_path_segments(path))
97
+
98
+ public_serve_compressed(server, path, '.br', 'br') if roda_opts[:public_brotli]
99
+ public_serve_compressed(server, path, '.gz', 'gzip') if roda_opts[:public_gzip]
100
+
101
+ if public_file_readable?(path)
102
+ halt public_serve(server, path)
103
+ end
104
+ end
105
+
104
106
  def public_serve_compressed(server, path, suffix, encoding)
105
107
  if env['HTTP_ACCEPT_ENCODING'] =~ /\b#{encoding}\b/
106
108
  compressed_path = path + suffix
@@ -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:
@@ -333,6 +333,25 @@ class Roda
333
333
  end
334
334
 
335
335
  module ClassMethods
336
+ # :nocov:
337
+ if COMPILED_METHOD_SUPPORT
338
+ # :nocov:
339
+ # If using compiled methods and there is an optimized layout, speed up
340
+ # access to the layout method to improve the performance of view.
341
+ def freeze
342
+ begin
343
+ _freeze_layout_method
344
+ rescue
345
+ # This is only for optimization, if any errors occur, they can be ignored.
346
+ # One possibility for error is the app doesn't use a layout, but doesn't
347
+ # specifically set the :layout=>false plugin option.
348
+ nil
349
+ end
350
+
351
+ super
352
+ end
353
+ end
354
+
336
355
  # Copy the rendering options into the subclass, duping
337
356
  # them as necessary to prevent changes in the subclass
338
357
  # affecting the parent class.
@@ -352,6 +371,27 @@ class Roda
352
371
  def render_opts
353
372
  opts[:render]
354
373
  end
374
+
375
+ private
376
+
377
+ # Precompile the layout method, to reduce method calls to look it up at runtime.
378
+ def _freeze_layout_method
379
+ if render_opts[:layout]
380
+ instance = allocate
381
+ instance.send(:retrieve_template, instance.send(:view_layout_opts, OPTS))
382
+
383
+ # :nocov:
384
+ if COMPILED_METHOD_SUPPORT
385
+ # :nocov:
386
+ if (layout_template = render_opts[:optimize_layout]) && !opts[:render][:optimized_layout_method_created]
387
+ instance.send(:retrieve_template, :template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout)
388
+ layout_method = opts[:render][:template_method_cache][:_roda_layout]
389
+ define_method(:_layout_method){layout_method}
390
+ opts[:render] = opts[:render].merge(:optimized_layout_method_created=>true)
391
+ end
392
+ end
393
+ end
394
+ end
355
395
  end
356
396
 
357
397
  module InstanceMethods
@@ -379,15 +419,19 @@ class Roda
379
419
  if optimized_template
380
420
  content = send(optimized_template, OPTS)
381
421
 
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
422
+ # First, check if the optimized layout method has already been created,
423
+ # and use it if so. This way avoids the extra conditional and local variable
424
+ # assignments in the next section.
425
+ if layout_method = _layout_method
426
+ return send(layout_method, OPTS){content}
427
+ end
389
428
 
390
- if layout_method
429
+ # If we have an optimized template method but no optimized layout method, create the
430
+ # optimized layout method if possible and use it. If you can't create the optimized
431
+ # layout method, fall through to the slower approach.
432
+ if layout_template = self.class.opts[:render][:optimize_layout]
433
+ retrieve_template(:template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout)
434
+ if layout_method = _layout_method
391
435
  return send(layout_method, OPTS){content}
392
436
  end
393
437
  end
@@ -428,6 +472,11 @@ class Roda
428
472
  method_cache[template]
429
473
  end
430
474
 
475
+ # Return a symbol containing the optimized layout method
476
+ def _layout_method
477
+ self.class.opts[:render][:template_method_cache][:_roda_layout]
478
+ end
479
+
431
480
  # Use an optimized render path for templates with a hash of locals. Returns the result
432
481
  # of the template render if the optimized path is used, or nil if the optimized
433
482
  # path is not used and the long method needs to be used.
@@ -471,11 +520,15 @@ class Roda
471
520
  end
472
521
  else
473
522
  # :nocov:
474
- def _cached_template_method(template)
523
+ def _cached_template_method(_)
475
524
  nil
476
525
  end
477
526
 
478
- def _cached_template_method_key(template)
527
+ def _cached_template_method_key(_)
528
+ nil
529
+ end
530
+
531
+ def _layout_method
479
532
  nil
480
533
  end
481
534
 
@@ -4,7 +4,7 @@
4
4
  class Roda
5
5
  module RodaPlugins
6
6
  # This plugin makes it easier to to respond to specific request data types. User agents can request
7
- # specific data types by either supplying an appropriate +Accept+ header
7
+ # specific data types by either supplying an appropriate +Accept+ request header
8
8
  # or by appending it as file extension to the path.
9
9
  #
10
10
  # Example:
@@ -50,6 +50,10 @@ class Roda
50
50
  # Content-Type header will be set to Roda's default (which you can override via
51
51
  # the default_headers plugin).
52
52
  #
53
+ # If the type routing is based on the +Accept+ request header and not the file extension,
54
+ # then an appropriate +Vary+ header will be set or appended to, so that HTTP caches do
55
+ # not serve the same result for requests with different +Accept+ headers.
56
+ #
53
57
  # To match custom extensions, use the :types option:
54
58
  #
55
59
  # plugin :type_routing, types: {
@@ -137,6 +141,7 @@ class Roda
137
141
  app::RodaRequest.send(:define_method, type) do |&block|
138
142
  on_type(type, &block)
139
143
  end
144
+ app::RodaRequest.send(:alias_method, type, type)
140
145
  end
141
146
 
142
147
  app.opts[:type_routing] = config.freeze
@@ -195,6 +200,7 @@ class Roda
195
200
  @env['HTTP_ACCEPT'].to_s.split(/\s*,\s*/).map do |part|
196
201
  mime, _= part.split(/\s*;\s*/, 2)
197
202
  if sym = mimes[mime]
203
+ response['Vary'] = (vary = response['Vary']) ? "#{vary}, Accept" : 'Accept'
198
204
  return sym
199
205
  end
200
206
  end
@@ -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
  #
@@ -609,10 +609,9 @@ class Roda
609
609
  when nil
610
610
  keys = (0...@obj.length)
611
611
 
612
- valid = case @obj
613
- when Array
612
+ valid = if @obj.is_a?(Array)
614
613
  true
615
- when Hash
614
+ else
616
615
  keys = keys.map(&:to_s)
617
616
  keys.all?{|k| @obj.has_key?(k)}
618
617
  end
@@ -725,7 +724,7 @@ class Roda
725
724
  raise Error, "parameter #{param_name(nil)} is not a hash" if do_raise
726
725
  return
727
726
  end
728
- present = @obj.has_key?(key)
727
+ present = !@obj[key].nil?
729
728
  when Integer
730
729
  unless @obj.is_a?(Array)
731
730
  raise Error, "parameter #{param_name(nil)} is not an array" if do_raise
@@ -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 = 35
7
+ RodaMinorVersion = 40
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.35.0
4
+ version: 3.40.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-08-14 00:00:00.000000000 Z
11
+ date: 2021-01-14 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,12 +198,24 @@ 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
211
205
  - doc/release_notes/3.33.0.txt
212
206
  - doc/release_notes/3.34.0.txt
213
207
  - doc/release_notes/3.35.0.txt
208
+ - doc/release_notes/3.36.0.txt
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.5.0.txt
215
+ - doc/release_notes/3.6.0.txt
216
+ - doc/release_notes/3.7.0.txt
217
+ - doc/release_notes/3.8.0.txt
218
+ - doc/release_notes/3.9.0.txt
214
219
  files:
215
220
  - CHANGELOG
216
221
  - MIT-LICENSE
@@ -247,7 +252,12 @@ files:
247
252
  - doc/release_notes/3.33.0.txt
248
253
  - doc/release_notes/3.34.0.txt
249
254
  - doc/release_notes/3.35.0.txt
255
+ - doc/release_notes/3.36.0.txt
256
+ - doc/release_notes/3.37.0.txt
257
+ - doc/release_notes/3.38.0.txt
258
+ - doc/release_notes/3.39.0.txt
250
259
  - doc/release_notes/3.4.0.txt
260
+ - doc/release_notes/3.40.0.txt
251
261
  - doc/release_notes/3.5.0.txt
252
262
  - doc/release_notes/3.6.0.txt
253
263
  - doc/release_notes/3.7.0.txt
@@ -273,6 +283,7 @@ files:
273
283
  - lib/roda/plugins/content_security_policy.rb
274
284
  - lib/roda/plugins/cookies.rb
275
285
  - lib/roda/plugins/csrf.rb
286
+ - lib/roda/plugins/custom_matchers.rb
276
287
  - lib/roda/plugins/default_headers.rb
277
288
  - lib/roda/plugins/default_status.rb
278
289
  - lib/roda/plugins/delay_build.rb
@@ -307,6 +318,7 @@ files:
307
318
  - lib/roda/plugins/middleware.rb
308
319
  - lib/roda/plugins/middleware_stack.rb
309
320
  - lib/roda/plugins/module_include.rb
321
+ - lib/roda/plugins/multi_public.rb
310
322
  - lib/roda/plugins/multi_route.rb
311
323
  - lib/roda/plugins/multi_run.rb
312
324
  - lib/roda/plugins/multi_view.rb
@@ -384,7 +396,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
384
396
  - !ruby/object:Gem::Version
385
397
  version: '0'
386
398
  requirements: []
387
- rubygems_version: 3.1.2
399
+ rubygems_version: 3.2.3
388
400
  signing_key:
389
401
  specification_version: 4
390
402
  summary: Routing tree web toolkit