roda 3.33.0 → 3.38.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: 0f9ede53406b16bff69c2115f7d35e808835e300ca866a5caadde9b9fad9d206
4
- data.tar.gz: f78eb239a3d15e83879b8a7272f07959a00b6d94e93595f6871841144d3d718b
3
+ metadata.gz: 5b28900f838d40c98d868225a5d2f0e1fcfd24ccf0bdbf770f0ccb79ac2ba2fe
4
+ data.tar.gz: 8f2c37b04485af74a1e8c2c68f5eeb31b0531d82b4f55c6df4145cfe344cf86f
5
5
  SHA512:
6
- metadata.gz: 45e3020a3509bacc5f39107659fca5e64ba25c322c18af6a62b976308f5bac2ac44a18d54bc82d9b1d4260e260883208ce8210ac2fb660e09b63b65401abfed6
7
- data.tar.gz: 5a00af14f4d6e01c472a23b59f5a51eec66fc55c2d8d34069de869d7ff487913ff6ab1d21023b8207f5fdf18ad6354f30520b3cf89d2491de4b330c1ae30f10a
6
+ metadata.gz: e2cb67bbc5509acba608530dc8641059cb4aa450f8cf0bdca82f57dabba48c751a50e770f902b0ed64da63d2988c89884e074991a49956d03188ea8efaab7793
7
+ data.tar.gz: 66a2ad67a5f4abd5e275f77eddde74f4dd6430c222cf208d9aafc01577fbfae548367f8b71864834cb4067ae530ece68df66165c4352bfc151b35bee8c5e2041
data/CHANGELOG CHANGED
@@ -1,3 +1,33 @@
1
+ = 3.38.0 (2020-11-16)
2
+
3
+ * Make error_email and error_mail plugins rescue invalid parameter errors when preparing the email body (jeremyevans)
4
+
5
+ = 3.37.0 (2020-10-16)
6
+
7
+ * Add custom_matchers plugin, for supporting arbitrary objects as matchers (jeremyevans)
8
+
9
+ = 3.36.0 (2020-09-14)
10
+
11
+ * Add multi_public plugin, for serving files from multiple public directories (jeremyevans)
12
+
13
+ * Support report-to directive in the content_security_policy plugin (jeremyevans)
14
+
15
+ * Add Vary response header when using type_routing plugin with Accept request header to prevent caching issues (jeremyevans)
16
+
17
+ = 3.35.0 (2020-08-14)
18
+
19
+ * Add r plugin for r method for accessing request, useful when r local variable is not in scope (jeremyevans)
20
+
21
+ * Warn when loading a plugin with arguments or a block if the plugin does not accept arguments or block (jeremyevans)
22
+
23
+ = 3.34.0 (2020-07-14)
24
+
25
+ * Remove unnecessary conditionals (jeremyevans)
26
+
27
+ * Allow loading the match_affix plugin with a single argument (jeremyevans)
28
+
29
+ * Do not include pre/post context sections if empty in the exception_page plugin (jeremyevans)
30
+
1
31
  = 3.33.0 (2020-06-16)
2
32
 
3
33
  * Add :brotli option to public plugin to supplement it to serve brotli-compressed files like :gzip does for gzipped files (hmdne) (#194)
@@ -0,0 +1,17 @@
1
+ = Improvements
2
+
3
+ * Multiple unneeded conditionals have been removed.
4
+
5
+ * pre_content and post_context sections in backtraces are no longer
6
+ included in the exception_page plugin output if they would be
7
+ empty.
8
+
9
+ * The match_affix plugin can be loaded again with a single argument.
10
+ It was originally designed to accept a single argument, but a bug
11
+ introduced in 2.29.0 made it require two arguments.
12
+
13
+ * Core Roda and all plugins that ship with Roda now have 100% branch
14
+ coverage.
15
+
16
+ * The sinatra_helpers plugin no longer emits statement not reached
17
+ warnings in verbose mode.
@@ -0,0 +1,12 @@
1
+ = New Features
2
+
3
+ * An r plugin has been added. This plugin adds an r method for the
4
+ request, useful for allowing the use of r.halt and r.redirect even
5
+ in methods where the r local variable is not in scope.
6
+
7
+ = Other Improvements
8
+
9
+ * Attempting to load a plugin with an argument or block when the plugin
10
+ does not accept arguments or a block now warns. This is because a
11
+ future update to support a block or an optional argument could break
12
+ the call.
@@ -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.
@@ -272,6 +272,12 @@ class Roda
272
272
  raise RodaError, "Cannot add a plugin to a frozen Roda class" if frozen?
273
273
  plugin = RodaPlugins.load_plugin(plugin) if plugin.is_a?(Symbol)
274
274
  raise RodaError, "Invalid plugin type: #{plugin.class.inspect}" unless plugin.is_a?(Module)
275
+
276
+ if !plugin.respond_to?(:load_dependencies) && !plugin.respond_to?(:configure) && (!args.empty? || block)
277
+ # RODA4: switch from warning to error
278
+ RodaPlugins.warn("Plugin #{plugin} does not accept arguments or a block, but arguments or a block was passed when loading this. This will raise an error in Roda 4.")
279
+ end
280
+
275
281
  plugin.load_dependencies(self, *args, &block) if plugin.respond_to?(:load_dependencies)
276
282
  include(plugin::InstanceMethods) if defined?(plugin::InstanceMethods)
277
283
  extend(plugin::ClassMethods) if defined?(plugin::ClassMethods)
@@ -34,7 +34,9 @@ class Roda
34
34
  module AllVerbs
35
35
  module RequestMethods
36
36
  %w'delete head options link patch put trace unlink'.each do |verb|
37
+ # :nocov:
37
38
  if ::Rack::Request.method_defined?("#{verb}?")
39
+ # :nocov:
38
40
  class_eval(<<-END, __FILE__, __LINE__+1)
39
41
  def #{verb}(*args, &block)
40
42
  _verb(args, &block) if #{verb}?
@@ -33,9 +33,7 @@ class Roda
33
33
  # elements. If the remaining elements could not be
34
34
  # matched, reset the state and continue to the next
35
35
  # entry in the array.
36
- def _match_array(arg, rest=nil)
37
- return super unless rest
38
-
36
+ def _match_array(arg, rest)
39
37
  path = @remaining_path
40
38
  captures = @captures
41
39
  caps = captures.dup
@@ -56,7 +56,6 @@ class Roda
56
56
  # currently have a routing block, setup an empty routing block so that things will still work if
57
57
  # a routing block isn't added.
58
58
  def self.configure(app)
59
- app.route{} unless app.route_block
60
59
  app.opts[:class_level_routes] ||= []
61
60
  end
62
61
 
@@ -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
+
@@ -1,6 +1,8 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ # :nocov:
3
4
  raise LoadError, "disallow_file_uploads plugin not supported on Rack <1.6" if Rack.release < '1.6'
5
+ # :nocov:
4
6
 
5
7
  #
6
8
  class Roda
@@ -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
 
@@ -252,11 +252,21 @@ END
252
252
  begin
253
253
  lineno -= 1
254
254
  lines = ::File.readlines(filename)
255
- pre_lineno = frame[:pre_context_lineno] = [lineno-context, 0].max
256
- frame[:pre_context] = lines[pre_lineno...lineno]
257
- frame[:context_line] = lines[lineno].chomp
258
- post_lineno = frame[:post_context_lineno] = [lineno+context, lines.size].min
259
- frame[:post_context] = lines[lineno+1..post_lineno]
255
+ if line = lines[lineno]
256
+ pre_lineno = [lineno-context, 0].max
257
+ if (pre_context = lines[pre_lineno...lineno]) && !pre_context.empty?
258
+ frame[:pre_context_lineno] = pre_lineno
259
+ frame[:pre_context] = pre_context
260
+ end
261
+
262
+ post_lineno = [lineno+context, lines.size].min
263
+ if (post_context = lines[lineno+1..post_lineno]) && !post_context.empty?
264
+ frame[:post_context_lineno] = post_lineno
265
+ frame[:post_context] = post_context
266
+ end
267
+
268
+ frame[:context_line] = line.chomp
269
+ end
260
270
  rescue
261
271
  end
262
272
 
@@ -28,7 +28,7 @@ class Roda
28
28
  #
29
29
  # This plugin automatically loads the placeholder_string_matchers plugin.
30
30
  module MatchAffix
31
- def self.load_dependencies(app, _prefix, _suffix)
31
+ def self.load_dependencies(app, _prefix, _suffix=nil)
32
32
  app.plugin :placeholder_string_matchers
33
33
  end
34
34
 
@@ -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
+
@@ -104,7 +104,9 @@ class Roda
104
104
  # arguments, record the verb used. If given an argument, add an is
105
105
  # check with the arguments.
106
106
  %w'get post delete head options link patch put trace unlink'.each do |verb|
107
+ # :nocov:
107
108
  if ::Rack::Request.method_defined?("#{verb}?")
109
+ # :nocov:
108
110
  class_eval(<<-END, __FILE__, __LINE__+1)
109
111
  def #{verb}(*args, &block)
110
112
  if (empty = args.empty?) && @_is_verbs
@@ -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
@@ -110,9 +112,7 @@ class Roda
110
112
  headers = res[1]
111
113
 
112
114
  unless res[0] == 304
113
- if mime_type = ::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
114
- headers['Content-Type'] = mime_type
115
- end
115
+ headers['Content-Type'] = ::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
116
116
  headers['Content-Encoding'] = encoding
117
117
  end
118
118
 
@@ -0,0 +1,35 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The r plugin adds an +r+ instance method that will return the request.
7
+ # This allows you to use common Roda idioms such as +r.halt+ and
8
+ # +r.redirect+ even when +r+ isn't a local variable in scope. Example:
9
+ #
10
+ # plugin :r
11
+ #
12
+ # def foo
13
+ # r.redirect "/bar"
14
+ # end
15
+ #
16
+ # route do |r|
17
+ # r.get "foo" do
18
+ # foo
19
+ # end
20
+ # r.get "bar" do
21
+ # "bar"
22
+ # end
23
+ # end
24
+ module R
25
+ module InstanceMethods
26
+ # The request object.
27
+ def r
28
+ @_request
29
+ end
30
+ end
31
+ end
32
+
33
+ register_plugin(:r, R)
34
+ end
35
+ end
@@ -287,7 +287,9 @@ class Roda
287
287
  false
288
288
  end
289
289
 
290
+ # :nocov:
290
291
  if COMPILED_METHOD_SUPPORT
292
+ # :nocov:
291
293
  # Compile a method in the given module with the given name that will
292
294
  # call the compiled template method, updating the compiled template method
293
295
  def define_compiled_method(roda_class, method_name, locals_keys=EMPTY_ARRAY)
@@ -337,7 +339,9 @@ class Roda
337
339
  def inherited(subclass)
338
340
  super
339
341
  opts = subclass.opts[:render] = subclass.opts[:render].dup
342
+ # :nocov:
340
343
  if COMPILED_METHOD_SUPPORT
344
+ # :nocov:
341
345
  opts[:template_method_cache] = (opts[:cache_class] || RodaCache).new
342
346
  end
343
347
  opts[:cache] = opts[:cache].dup
@@ -43,7 +43,9 @@ class Roda
43
43
  module InstanceMethods
44
44
  private
45
45
 
46
+ # :nocov:
46
47
  if Render::COMPILED_METHOD_SUPPORT
48
+ # :nocov:
47
49
  # Disable use of cached templates, since it assumes a render/view call with no
48
50
  # options will have no locals.
49
51
  def _cached_template_method(template)
@@ -374,7 +374,7 @@ class Roda
374
374
 
375
375
  module ResponseMethods
376
376
  # Set or retrieve the response status code.
377
- def status(value = (return @status; nil))
377
+ def status(value = nil || (return @status))
378
378
  @status = value
379
379
  end
380
380
 
@@ -401,7 +401,7 @@ class Roda
401
401
 
402
402
  # Set multiple response headers with Hash, or return the headers if no
403
403
  # argument is given.
404
- def headers(hash = (return @headers; nil))
404
+ def headers(hash = nil || (return @headers))
405
405
  @headers.merge!(hash)
406
406
  end
407
407
 
@@ -412,7 +412,7 @@ class Roda
412
412
 
413
413
  # Set the Content-Type of the response body given a media type or file
414
414
  # extension. See plugin documentation for options.
415
- def content_type(type = (return @headers["Content-Type"]; nil), opts = OPTS)
415
+ def content_type(type = nil || (return @headers["Content-Type"]), opts = OPTS)
416
416
  unless (mime_type = mime_type(type) || opts[:default])
417
417
  raise RodaError, "Unknown media type: #{type}"
418
418
  end
@@ -478,7 +478,7 @@ class Roda
478
478
  # If a type and value are given, set the value in Rack's MIME registry.
479
479
  # If only a type is given, lookup the type in Rack's MIME registry and
480
480
  # return it.
481
- def mime_type(type=(return; nil), value = nil)
481
+ def mime_type(type=nil || (return), value = nil)
482
482
  return type.to_s if type.to_s.include?('/')
483
483
  type = ".#{type}" unless type.to_s[0] == ?.
484
484
  if value
@@ -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: {
@@ -195,6 +199,7 @@ class Roda
195
199
  @env['HTTP_ACCEPT'].to_s.split(/\s*,\s*/).map do |part|
196
200
  mime, _= part.split(/\s*;\s*/, 2)
197
201
  if sym = mimes[mime]
202
+ response['Vary'] = (vary = response['Vary']) ? "#{vary}, Accept" : 'Accept'
198
203
  return sym
199
204
  end
200
205
  end
@@ -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
@@ -742,9 +741,7 @@ class Roda
742
741
  return
743
742
  end
744
743
 
745
- if v = self[key]
746
- v.subkey(keys, do_raise)
747
- end
744
+ self[key].subkey(keys, do_raise)
748
745
  rescue => e
749
746
  handle_error(key, reason, e)
750
747
  end
@@ -808,10 +805,10 @@ class Roda
808
805
  @nested_params = nil
809
806
 
810
807
  if capturing_started
811
- # Unset capturing if capturing was not already started.
808
+ # Unset capturing if capturing was already started.
812
809
  @capture = nil
813
810
  else
814
- # If capturing was already started, update cached nested params
811
+ # If capturing was not already started, update cached nested params
815
812
  # before resetting symbolize setting.
816
813
  @nested_params = nested_params
817
814
  end
@@ -878,7 +875,7 @@ class Roda
878
875
  def handle_error(key, reason, e, do_raise=false)
879
876
  case e
880
877
  when String
881
- handle_error(key, reason, Error.new(e), do_raise=false)
878
+ handle_error(key, reason, Error.new(e), do_raise)
882
879
  when Error, ArgumentError
883
880
  if @capture && (le = @capture.last) && le == e
884
881
  raise e if do_raise
@@ -126,7 +126,9 @@ class Roda
126
126
 
127
127
  private
128
128
 
129
+ # :nocov:
129
130
  if Render::COMPILED_METHOD_SUPPORT
131
+ # :nocov:
130
132
  # Return nil if using custom view or layout options.
131
133
  # If using a view subdir, prefix the template key with the subdir.
132
134
  def _cached_template_method_key(template)
@@ -466,10 +466,8 @@ class Roda
466
466
  rp = @remaining_path
467
467
  if rp.getbyte(0) == 47
468
468
  if last = rp.index('/', 1)
469
- if last > 1
470
- @captures << rp[1, last-1]
471
- @remaining_path = rp[last, rp.length]
472
- end
469
+ @captures << rp[1, last-1]
470
+ @remaining_path = rp[last, rp.length]
473
471
  elsif rp.length > 1
474
472
  @captures << rp[1,rp.length]
475
473
  @remaining_path = ""
@@ -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 = 33
7
+ RodaMinorVersion = 38
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.33.0
4
+ version: 3.38.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-06-16 00:00:00.000000000 Z
11
+ date: 2020-11-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -209,6 +209,11 @@ extra_rdoc_files:
209
209
  - doc/release_notes/3.31.0.txt
210
210
  - doc/release_notes/3.32.0.txt
211
211
  - doc/release_notes/3.33.0.txt
212
+ - doc/release_notes/3.34.0.txt
213
+ - doc/release_notes/3.35.0.txt
214
+ - doc/release_notes/3.36.0.txt
215
+ - doc/release_notes/3.37.0.txt
216
+ - doc/release_notes/3.38.0.txt
212
217
  files:
213
218
  - CHANGELOG
214
219
  - MIT-LICENSE
@@ -243,6 +248,11 @@ files:
243
248
  - doc/release_notes/3.31.0.txt
244
249
  - doc/release_notes/3.32.0.txt
245
250
  - doc/release_notes/3.33.0.txt
251
+ - doc/release_notes/3.34.0.txt
252
+ - doc/release_notes/3.35.0.txt
253
+ - doc/release_notes/3.36.0.txt
254
+ - doc/release_notes/3.37.0.txt
255
+ - doc/release_notes/3.38.0.txt
246
256
  - doc/release_notes/3.4.0.txt
247
257
  - doc/release_notes/3.5.0.txt
248
258
  - doc/release_notes/3.6.0.txt
@@ -269,6 +279,7 @@ files:
269
279
  - lib/roda/plugins/content_security_policy.rb
270
280
  - lib/roda/plugins/cookies.rb
271
281
  - lib/roda/plugins/csrf.rb
282
+ - lib/roda/plugins/custom_matchers.rb
272
283
  - lib/roda/plugins/default_headers.rb
273
284
  - lib/roda/plugins/default_status.rb
274
285
  - lib/roda/plugins/delay_build.rb
@@ -303,6 +314,7 @@ files:
303
314
  - lib/roda/plugins/middleware.rb
304
315
  - lib/roda/plugins/middleware_stack.rb
305
316
  - lib/roda/plugins/module_include.rb
317
+ - lib/roda/plugins/multi_public.rb
306
318
  - lib/roda/plugins/multi_route.rb
307
319
  - lib/roda/plugins/multi_run.rb
308
320
  - lib/roda/plugins/multi_view.rb
@@ -322,6 +334,7 @@ files:
322
334
  - lib/roda/plugins/placeholder_string_matchers.rb
323
335
  - lib/roda/plugins/precompile_templates.rb
324
336
  - lib/roda/plugins/public.rb
337
+ - lib/roda/plugins/r.rb
325
338
  - lib/roda/plugins/relative_path.rb
326
339
  - lib/roda/plugins/render.rb
327
340
  - lib/roda/plugins/render_each.rb
@@ -379,7 +392,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
379
392
  - !ruby/object:Gem::Version
380
393
  version: '0'
381
394
  requirements: []
382
- rubygems_version: 3.1.2
395
+ rubygems_version: 3.1.4
383
396
  signing_key:
384
397
  specification_version: 4
385
398
  summary: Routing tree web toolkit