roda 3.107.0 → 3.109.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/lib/roda/opts.rb +22 -0
  3. data/lib/roda/plugins/Integer_matcher_max.rb +1 -1
  4. data/lib/roda/plugins/_add_script_name.rb +14 -0
  5. data/lib/roda/plugins/_base64.rb +2 -2
  6. data/lib/roda/plugins/_json.rb +22 -0
  7. data/lib/roda/plugins/_optimized_matching.rb +1 -0
  8. data/lib/roda/plugins/_symbol_class_matchers.rb +6 -6
  9. data/lib/roda/plugins/additional_render_engines.rb +5 -1
  10. data/lib/roda/plugins/additional_view_directories.rb +5 -1
  11. data/lib/roda/plugins/all_verbs.rb +2 -2
  12. data/lib/roda/plugins/assets.rb +11 -14
  13. data/lib/roda/plugins/autoload_hash_branches.rb +9 -3
  14. data/lib/roda/plugins/autoload_named_routes.rb +9 -3
  15. data/lib/roda/plugins/bearer_token.rb +2 -2
  16. data/lib/roda/plugins/break.rb +9 -1
  17. data/lib/roda/plugins/caching.rb +12 -12
  18. data/lib/roda/plugins/capture_erb.rb +5 -1
  19. data/lib/roda/plugins/chunked.rb +11 -5
  20. data/lib/roda/plugins/class_level_routing.rb +5 -3
  21. data/lib/roda/plugins/class_matchers.rb +3 -1
  22. data/lib/roda/plugins/common_logger.rb +11 -7
  23. data/lib/roda/plugins/conditional_sessions.rb +1 -1
  24. data/lib/roda/plugins/content_for.rb +5 -1
  25. data/lib/roda/plugins/content_security_policy.rb +6 -2
  26. data/lib/roda/plugins/cookie_flags.rb +8 -4
  27. data/lib/roda/plugins/cookies.rb +7 -3
  28. data/lib/roda/plugins/custom_block_results.rb +5 -3
  29. data/lib/roda/plugins/custom_matchers.rb +4 -2
  30. data/lib/roda/plugins/default_headers.rb +2 -5
  31. data/lib/roda/plugins/default_status.rb +1 -0
  32. data/lib/roda/plugins/each_part.rb +23 -27
  33. data/lib/roda/plugins/environments.rb +3 -7
  34. data/lib/roda/plugins/error_email.rb +11 -7
  35. data/lib/roda/plugins/error_handler.rb +5 -3
  36. data/lib/roda/plugins/error_mail.rb +6 -2
  37. data/lib/roda/plugins/exception_page.rb +2 -2
  38. data/lib/roda/plugins/h.rb +4 -4
  39. data/lib/roda/plugins/hash_branch_view_subdir.rb +6 -4
  40. data/lib/roda/plugins/hash_branches.rb +15 -9
  41. data/lib/roda/plugins/hash_paths.rb +11 -7
  42. data/lib/roda/plugins/hash_public.rb +13 -9
  43. data/lib/roda/plugins/hash_public_cache.rb +9 -7
  44. data/lib/roda/plugins/hash_routes.rb +5 -3
  45. data/lib/roda/plugins/heartbeat.rb +6 -2
  46. data/lib/roda/plugins/hmac_paths.rb +36 -32
  47. data/lib/roda/plugins/hooks.rb +13 -10
  48. data/lib/roda/plugins/host_authorization.rb +7 -2
  49. data/lib/roda/plugins/host_routing.rb +36 -6
  50. data/lib/roda/plugins/invalid_request_body.rb +22 -4
  51. data/lib/roda/plugins/ip_from_header.rb +5 -1
  52. data/lib/roda/plugins/json.rb +10 -8
  53. data/lib/roda/plugins/json_parser.rb +43 -11
  54. data/lib/roda/plugins/mail_processor.rb +11 -8
  55. data/lib/roda/plugins/mailer.rb +9 -7
  56. data/lib/roda/plugins/match_affix.rb +6 -1
  57. data/lib/roda/plugins/match_hook_args.rb +7 -5
  58. data/lib/roda/plugins/middleware.rb +13 -7
  59. data/lib/roda/plugins/module_include.rb +1 -1
  60. data/lib/roda/plugins/multi_public.rb +6 -2
  61. data/lib/roda/plugins/multi_route.rb +1 -1
  62. data/lib/roda/plugins/multi_run.rb +13 -17
  63. data/lib/roda/plugins/named_routes.rb +8 -6
  64. data/lib/roda/plugins/named_templates.rb +5 -3
  65. data/lib/roda/plugins/optimized_segment_matchers.rb +13 -6
  66. data/lib/roda/plugins/optimized_string_matchers.rb +6 -3
  67. data/lib/roda/plugins/params_capturing.rb +16 -7
  68. data/lib/roda/plugins/params_capturing_restore.rb +72 -0
  69. data/lib/roda/plugins/part.rb +4 -4
  70. data/lib/roda/plugins/pass.rb +4 -1
  71. data/lib/roda/plugins/path.rb +22 -20
  72. data/lib/roda/plugins/path_matchers.rb +5 -1
  73. data/lib/roda/plugins/path_rewriter.rb +8 -5
  74. data/lib/roda/plugins/permissions_policy.rb +7 -3
  75. data/lib/roda/plugins/public.rb +15 -14
  76. data/lib/roda/plugins/recheck_precompiled_assets.rb +1 -1
  77. data/lib/roda/plugins/redirect_http_to_https.rb +6 -3
  78. data/lib/roda/plugins/render.rb +58 -61
  79. data/lib/roda/plugins/render_coverage.rb +12 -8
  80. data/lib/roda/plugins/render_each.rb +2 -2
  81. data/lib/roda/plugins/render_locals.rb +12 -6
  82. data/lib/roda/plugins/request_aref.rb +1 -1
  83. data/lib/roda/plugins/response_content_type.rb +7 -3
  84. data/lib/roda/plugins/route_csrf.rb +42 -5
  85. data/lib/roda/plugins/run_append_slash.rb +12 -4
  86. data/lib/roda/plugins/sec_fetch_site_csrf.rb +11 -4
  87. data/lib/roda/plugins/sessions.rb +17 -13
  88. data/lib/roda/plugins/shape_friendly.rb +7 -5
  89. data/lib/roda/plugins/sinatra_helpers.rb +7 -3
  90. data/lib/roda/plugins/static.rb +3 -0
  91. data/lib/roda/plugins/status_handler.rb +5 -3
  92. data/lib/roda/plugins/strip_path_prefix.rb +4 -2
  93. data/lib/roda/plugins/symbol_matchers.rb +3 -1
  94. data/lib/roda/plugins/symbol_views.rb +1 -1
  95. data/lib/roda/plugins/timestamp_public.rb +8 -4
  96. data/lib/roda/plugins/type_routing.rb +10 -6
  97. data/lib/roda/plugins/typecast_params.rb +2 -2
  98. data/lib/roda/plugins/typecast_params_sized_integers.rb +1 -1
  99. data/lib/roda/plugins/url_escape.rb +2 -14
  100. data/lib/roda/plugins/view_options.rb +3 -5
  101. data/lib/roda/plugins.rb +14 -4
  102. data/lib/roda/request.rb +25 -9
  103. data/lib/roda/version.rb +1 -1
  104. data/lib/roda.rb +32 -21
  105. metadata +7 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84bb84bd98ebe0d00d48315d9036ae1c22d93c92715922a6dba297f225c9fec7
4
- data.tar.gz: 7cf2abbe3656ec22aef206a4e895c2520a8f80dc0ed11ec058a8a5f4cf3194b5
3
+ metadata.gz: f8fe0436b7983ecbf3bbbf1cf8d7d8867f87184550ef097d6fad894a17211c5e
4
+ data.tar.gz: a19800b130b13b745d3ff6a77c22ac88e4c44b70a778c243e07212c0fcebd019
5
5
  SHA512:
6
- metadata.gz: 874ed7d5065e77c593bccd1939d3742acf3e65a0b6ea6eb95e02cd6ce9ca55c9819cfafe6ecfc61ce548075832b91c68800e7f5ae35774efb60949f0a86cb1ae
7
- data.tar.gz: 40a37368dfc79527849fec46d615c26680cad15dd590b8b6a3dba0fa2809c2b90bacefa70f65b78566f88c62485f97afd715494a214fcc87050125ac7743d519
6
+ metadata.gz: e0778c049972903e98c14287074ed0833eb63b201834c61ed8731cafb9be5da523963e931eb8ed668a975f61682d866c6ffcd1e61a4aaa19d62ec663f2318fc1
7
+ data.tar.gz: 17e2cd71b7593a18b398fd2965a2673280e11f76db7f02f9de1eaa604e4632857fbe8c1875cfd9fc3ec9d6af2152ad4a63d757065a1ae53cb46766de5690238a
data/lib/roda/opts.rb ADDED
@@ -0,0 +1,22 @@
1
+ # frozen-string-literal: true
2
+
3
+ class Roda
4
+ class RodaOpts < Hash
5
+ # The Roda class to set instance variables on when calling #[]=
6
+ attr_writer :roda_class
7
+
8
+ # If duplicating the hash, detach the Roda class.
9
+ def initialize_copy(_)
10
+ super
11
+ @roda_class = nil
12
+ end
13
+
14
+ # Set an <tt>@opt_*</tt> instance variable in the Roda class if it is tied
15
+ # to a Roda class.
16
+ def []=(k, v)
17
+ super
18
+ @roda_class.instance_variable_set(:"@opt_#{k}", v) if @roda_class && Symbol === k
19
+ v
20
+ end
21
+ end
22
+ end
@@ -23,7 +23,7 @@ class Roda
23
23
  module IntegerMatcherMax
24
24
  def self.configure(app, max=nil)
25
25
  if max
26
- app.class_eval do
26
+ app.class_exec do
27
27
  meth = :_max_value_convert_class_Integer
28
28
  define_method(meth){max}
29
29
  alias_method meth, meth
@@ -0,0 +1,14 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ module AddScriptName_
7
+ module ClassMethods
8
+ RodaPlugins.opt_attr_reader(self, :add_script_name, name: :add_script_name?)
9
+ end
10
+ end
11
+
12
+ register_plugin(:_add_script_name, AddScriptName_)
13
+ end
14
+ end
@@ -9,12 +9,12 @@ class Roda
9
9
  def decode64(str)
10
10
  str.unpack1("m0")
11
11
  end
12
- # :nocov:
12
+ # simplecov:disable
13
13
  else
14
14
  def decode64(str)
15
15
  str.unpack("m0")[0]
16
16
  end
17
- # :nocov:
17
+ # simplecov:enable
18
18
  end
19
19
 
20
20
  def urlsafe_encode64(bin)
@@ -0,0 +1,22 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'json'
4
+
5
+ #
6
+ class Roda
7
+ module RodaPlugins
8
+ module JSON_
9
+ module ClassMethods
10
+ def json_parser
11
+ @opt_json_parser || ::JSON.method(:parse)
12
+ end
13
+
14
+ def json_serializer
15
+ @opt_json_serializer || :to_json.to_proc
16
+ end
17
+ end
18
+ end
19
+
20
+ register_plugin(:_json, JSON_)
21
+ end
22
+ end
@@ -44,6 +44,7 @@ class Roda
44
44
  rp = @remaining_path
45
45
  if rp.getbyte(0) == 47
46
46
  if last = rp.index('/', 1)
47
+ return false if last == 1
47
48
  @remaining_path = rp[last, rp.length]
48
49
  always{yield rp[1, last-1]}
49
50
  elsif (len = rp.length) > 1
@@ -26,11 +26,11 @@ class Roda
26
26
  regexp = matcher
27
27
  consume_regexp = self::RodaRequest.send(:consume_pattern, regexp)
28
28
  when Symbol
29
- unless opts[:symbol_matchers]
29
+ unless respond_to?(:symbol_matchers)
30
30
  raise RodaError, "cannot provide Symbol matcher to class_matcher unless using symbol_matchers plugin: #{matcher.inspect}"
31
31
  end
32
32
 
33
- regexp, consume_regexp, convert_meth, consume_meth = opts[:symbol_matchers][matcher]
33
+ regexp, consume_regexp, convert_meth, consume_meth = symbol_matchers[matcher]
34
34
 
35
35
  unless regexp
36
36
  raise RodaError, "unregistered symbol matcher given to #{type}_matcher: #{matcher.inspect}"
@@ -38,11 +38,11 @@ class Roda
38
38
 
39
39
  block = _merge_matcher_blocks(type, obj, block, convert_meth)
40
40
  when Class
41
- unless opts[:class_matchers]
41
+ unless respond_to?(:class_matchers)
42
42
  raise RodaError, "cannot provide Class matcher to symbol_matcher unless using class_matchers plugin: #{matcher.inspect}"
43
43
  end
44
44
 
45
- regexp, consume_regexp, convert_meth, consume_meth = opts[:class_matchers][matcher]
45
+ regexp, consume_regexp, convert_meth, consume_meth = class_matchers[matcher]
46
46
  unless regexp
47
47
  raise RodaError, "unregistered class matcher given to #{type}_matcher: #{matcher.inspect}"
48
48
  end
@@ -60,9 +60,9 @@ class Roda
60
60
  end
61
61
 
62
62
  consume_meth ||= options[:segment] ? :_consume_single_segment : :consume
63
- array = opts[:"#{type}_matchers"][obj] = [regexp, consume_regexp, convert_meth, consume_meth].freeze
63
+ array = (obj.is_a?(Symbol) ? symbol_matchers : class_matchers)[obj] = [regexp, consume_regexp, convert_meth, consume_meth].freeze
64
64
 
65
- self::RodaRequest.class_eval do
65
+ self::RodaRequest.class_exec do
66
66
  class_exec(meth, array, &request_class_block)
67
67
  private meth
68
68
  end
@@ -34,6 +34,10 @@ class Roda
34
34
  app.opts[:additional_render_engines] = render_engines.dup.freeze
35
35
  end
36
36
 
37
+ module ClassMethods
38
+ RodaPlugins.opt_attr_reader(self, :additional_render_engines)
39
+ end
40
+
37
41
  module InstanceMethods
38
42
  private
39
43
 
@@ -45,7 +49,7 @@ class Roda
45
49
  orig_path = super
46
50
 
47
51
  unless File.file?(orig_path)
48
- self.opts[:additional_render_engines].each do |engine|
52
+ self.class.additional_render_engines.each do |engine|
49
53
  path = super(opts.merge(:engine=>engine))
50
54
  return path if File.file?(path)
51
55
  end
@@ -39,6 +39,10 @@ class Roda
39
39
  app.plugin :render, :allowed_paths=>(app.opts[:render][:allowed_paths] + view_dirs).uniq.freeze
40
40
  end
41
41
 
42
+ module ClassMethods
43
+ RodaPlugins.opt_attr_reader(self, :additional_view_directories)
44
+ end
45
+
42
46
  module InstanceMethods
43
47
  private
44
48
 
@@ -50,7 +54,7 @@ class Roda
50
54
  orig_path = super
51
55
 
52
56
  unless File.file?(orig_path)
53
- self.opts[:additional_view_directories].each do |view_dir|
57
+ self.class.additional_view_directories.each do |view_dir|
54
58
  path = super(opts.merge(:views=>view_dir))
55
59
  return path if File.file?(path)
56
60
  end
@@ -6,7 +6,7 @@ class Roda
6
6
  # The all_verbs plugin adds methods for http verbs other than
7
7
  # get and post. The following verbs are added, assuming
8
8
  # rack handles them: delete, head, options, link, patch, put,
9
- # trace, unlink.
9
+ # query, trace, unlink.
10
10
  #
11
11
  # These methods operate just like Roda's default get and post
12
12
  # methods, so using them without any arguments just checks for
@@ -33,7 +33,7 @@ class Roda
33
33
  # isn't documentation for the individual methods created.
34
34
  module AllVerbs
35
35
  module RequestMethods
36
- %w'delete head options link patch put trace unlink'.each do |verb|
36
+ %w'delete head options link patch put query trace unlink'.each do |verb|
37
37
  if ::Rack::Request.method_defined?("#{verb}?")
38
38
  class_eval(<<-END, __FILE__, __LINE__+1)
39
39
  def #{verb}(*args, &block)
@@ -371,6 +371,8 @@ class Roda
371
371
  # Load the render, caching, and h plugins, since the assets plugin
372
372
  # depends on them.
373
373
  def self.load_dependencies(app, opts = OPTS)
374
+ app.plugin :_json
375
+ app.plugin :_add_script_name
374
376
  app.plugin :render
375
377
  app.plugin :caching
376
378
  app.plugin :h
@@ -401,7 +403,7 @@ class Roda
401
403
  app.opts[:assets] = opts.dup
402
404
  app.opts[:assets][:orig_opts] = opts
403
405
  end
404
- opts = app.opts[:assets]
406
+ opts = app.assets_opts
405
407
  opts[:path] = app.expand_path(opts[:path]||"assets").freeze
406
408
  opts[:public] = app.expand_path(opts[:public]||"public").freeze
407
409
 
@@ -420,7 +422,6 @@ class Roda
420
422
  end
421
423
 
422
424
  if opts[:precompiled] && !opts[:compiled] && ::File.exist?(opts[:precompiled])
423
- require 'json'
424
425
  opts[:compiled] = app.send(:_precompiled_asset_metadata, opts[:precompiled])
425
426
  end
426
427
 
@@ -498,10 +499,7 @@ class Roda
498
499
  end
499
500
 
500
501
  module ClassMethods
501
- # Return the assets options for this class.
502
- def assets_opts
503
- opts[:assets]
504
- end
502
+ RodaPlugins.opt_attr_reader(self, :assets, name: :assets_opts)
505
503
 
506
504
  # Compile options for the given asset type. If no asset_type
507
505
  # is given, compile both the :css and :js asset types. You
@@ -522,10 +520,9 @@ class Roda
522
520
  end
523
521
 
524
522
  if precompile_file = assets_opts[:precompiled]
525
- require 'json'
526
523
  ::FileUtils.mkdir_p(File.dirname(precompile_file))
527
524
  tmp_file = "#{precompile_file}.tmp"
528
- ::File.open(tmp_file, 'wb'){|f| f.write((opts[:json_serializer] || :to_json.to_proc).call(assets_opts[:compiled]))}
525
+ ::File.open(tmp_file, 'wb'){|f| f.write(json_serializer.call(assets_opts[:compiled]))}
529
526
  ::File.rename(tmp_file, precompile_file)
530
527
  end
531
528
 
@@ -558,7 +555,7 @@ class Roda
558
555
 
559
556
  # The precompiled asset metadata stored in the given file
560
557
  def _precompiled_asset_metadata(file)
561
- (opts[:json_parser] || ::JSON.method(:parse)).call(::File.read(file))
558
+ json_parser.call(::File.read(file))
562
559
  end
563
560
 
564
561
  # Compile each array of files for the given type into a single
@@ -659,9 +656,9 @@ class Roda
659
656
  begin
660
657
  require 'uglifier'
661
658
  rescue => e
662
- # :nocov:
659
+ # simplecov:disable
663
660
  raise CompressorNotFound, "#{e.class}: #{e.message}", e.backtrace
664
- # :nocov:
661
+ # simplecov:enable
665
662
  end
666
663
 
667
664
  Uglifier.compile(content)
@@ -689,11 +686,11 @@ class Roda
689
686
  digest = begin
690
687
  require 'openssl'
691
688
  ::OpenSSL::Digest
692
- # :nocov:
689
+ # simplecov:disable
693
690
  rescue LoadError
694
691
  require 'digest/sha2'
695
692
  ::Digest
696
- # :nocov:
693
+ # simplecov:enable
697
694
  end
698
695
  digest.const_get(algo.to_s.upcase).hexdigest(content)
699
696
  end
@@ -711,7 +708,7 @@ class Roda
711
708
  end
712
709
  stype = ltype.to_s
713
710
 
714
- url_prefix = request.script_name if self.class.opts[:add_script_name]
711
+ url_prefix = request.script_name if self.class.add_script_name?
715
712
  relative_paths = o[:relative_paths]
716
713
 
717
714
  paths = if o[:compiled]
@@ -38,13 +38,15 @@ class Roda
38
38
  end
39
39
 
40
40
  module ClassMethods
41
+ RodaPlugins.opt_attr_reader(self, :autoload_hash_branch_files)
42
+
41
43
  # Autoload the given file when there is request for the hash branch.
42
44
  # The given file should configure the hash branch specified.
43
45
  def autoload_hash_branch(namespace='', segment, file)
44
46
  segment = "/#{segment}"
45
47
  file = File.expand_path(file)
46
- opts[:autoload_hash_branch_files] << file
47
- routes = opts[:hash_branches][namespace] ||= {}
48
+ autoload_hash_branch_files << file
49
+ routes = hash_branches[namespace] ||= {}
48
50
  meth = routes[segment] = define_roda_method(routes[segment] || "hash_branch_#{namespace}_#{segment}", 1) do |r|
49
51
  loc = method(routes[segment]).source_location
50
52
  require file
@@ -68,7 +70,11 @@ class Roda
68
70
 
69
71
  # Eagerly load all hash branches when freezing the application.
70
72
  def freeze
71
- opts.delete(:autoload_hash_branch_files).each{|file| require file} unless opts.frozen?
73
+ unless opts.frozen?
74
+ autoload_hash_branch_files.each{|file| require file}
75
+ opts[:autoload_hash_branch_files] = nil
76
+ opts.delete(:autoload_hash_branch_files)
77
+ end
72
78
  super
73
79
  end
74
80
  end
@@ -35,12 +35,14 @@ class Roda
35
35
  end
36
36
 
37
37
  module ClassMethods
38
+ RodaPlugins.opt_attr_reader(self, :autoload_named_route_files)
39
+
38
40
  # Autoload the given file when there is request for the named route.
39
41
  # The given file should configure the named route specified.
40
42
  def autoload_named_route(namespace=nil, name, file)
41
43
  file = File.expand_path(file)
42
- opts[:autoload_named_route_files] << file
43
- routes = opts[:namespaced_routes][namespace] ||= {}
44
+ autoload_named_route_files << file
45
+ routes = namespaced_routes[namespace] ||= {}
44
46
  meth = routes[name] = define_roda_method(routes[name] || "named_routes_#{namespace}_#{name}", 1) do |r|
45
47
  loc = method(routes[name]).source_location
46
48
  require file
@@ -54,7 +56,11 @@ class Roda
54
56
 
55
57
  # Eagerly load all autoloaded named routes when freezing the application.
56
58
  def freeze
57
- opts.delete(:autoload_named_route_files).each{|file| require file} unless opts.frozen?
59
+ unless opts.frozen?
60
+ autoload_named_route_files.each{|file| require file}
61
+ opts[:autoload_named_route_files] = nil
62
+ opts.delete(:autoload_named_route_files)
63
+ end
58
64
  super
59
65
  end
60
66
  end
@@ -8,9 +8,9 @@ class Roda
8
8
  # in the authorization header will be recognized as long as they start
9
9
  # with the case insensitive string "bearer ".
10
10
  module BearerToken
11
- # :nocov:
11
+ # simplecov:disable
12
12
  METHOD = RUBY_VERSION >= "2.4" ? :match? : :match
13
- # :nocov:
13
+ # simplecov:enable
14
14
 
15
15
  module RequestMethods
16
16
  # Return the bearer token for the request if there is one in the
@@ -22,12 +22,20 @@ class Roda
22
22
  # end
23
23
  #
24
24
  # This provides the same basic feature as the pass plugin, but
25
- # uses Ruby's standard control flow primative instead of a
25
+ # uses Ruby's standard control flow primitive instead of a
26
26
  # separate method.
27
27
  module Break
28
28
  module RequestMethods
29
29
  private
30
30
 
31
+ # Handle passing inside the match block.
32
+ def always
33
+ rp = @remaining_path
34
+ super
35
+ ensure
36
+ @remaining_path = rp
37
+ end
38
+
31
39
  # Handle break inside match blocks, restoring remaining path.
32
40
  def if_match(_)
33
41
  rp = @remaining_path
@@ -75,28 +75,27 @@ class Roda
75
75
  # Set the last modified time of the resource using the Last-Modified header.
76
76
  # The +time+ argument should be a Time instance.
77
77
  #
78
- # If the current request includes an If-Modified-Since header that is
79
- # equal or later than the time specified, immediately returns a response
80
- # with a 304 status.
81
- #
82
78
  # If the current request includes an If-Unmodified-Since header that is
83
79
  # before than the time specified, immediately returns a response
84
80
  # with a 412 status.
81
+ #
82
+ # If the current request does not include an If-None-Match header and
83
+ # includes an If-Modified-Since header that is equal or later than the
84
+ # time specified, immediately returns a response with a 304 status.
85
85
  def last_modified(time)
86
86
  return unless time
87
87
  res = response
88
88
  e = env
89
89
  res[RodaResponseHeaders::LAST_MODIFIED] = time.httpdate
90
- return if e['HTTP_IF_NONE_MATCH']
91
90
  status = res.status
92
91
 
93
- if (!status || status == 200) && (ims = time_from_header(e['HTTP_IF_MODIFIED_SINCE'])) && ims >= time.to_i
94
- res.status = 304
92
+ if (!status || (status >= 200 && status < 300) || status == 412) && (ius = time_from_header(e['HTTP_IF_UNMODIFIED_SINCE'])) && ius < time.to_i
93
+ res.status = 412
95
94
  halt
96
95
  end
97
96
 
98
- if (!status || (status >= 200 && status < 300) || status == 412) && (ius = time_from_header(e['HTTP_IF_UNMODIFIED_SINCE'])) && ius < time.to_i
99
- res.status = 412
97
+ if !e['HTTP_IF_NONE_MATCH'] && (!status || status == 200) && (ims = time_from_header(e['HTTP_IF_MODIFIED_SINCE'])) && ims >= time.to_i
98
+ res.status = 304
100
99
  halt
101
100
  end
102
101
  end
@@ -113,8 +112,9 @@ class Roda
113
112
  # matching etag, immediately returns a response with a 304 or 412 status,
114
113
  # depending on the request method.
115
114
  #
116
- # When the current request includes an If-Match header with a
117
- # etag that doesn't match, immediately returns a response with a 412 status.
115
+ # When the current request includes an If-Match header with an etag that
116
+ # doesn't match, or where the :weak option was used, immediately returns a
117
+ # response with a 412 status.
118
118
  def etag(value, opts=OPTS)
119
119
  # Before touching this code, please double check RFC 2616 14.24 and 14.26.
120
120
  weak = opts[:weak]
@@ -132,7 +132,7 @@ class Roda
132
132
  end
133
133
 
134
134
  if ifm = e['HTTP_IF_MATCH']
135
- unless etag_matches?(ifm, etag, new_resource)
135
+ if weak || !etag_matches?(ifm, etag, new_resource)
136
136
  res.status = 412
137
137
  halt
138
138
  end
@@ -51,6 +51,10 @@ class Roda
51
51
  app.opts[:capture_erb_returns] = opts[:returns] if opts.has_key?(:returns)
52
52
  end
53
53
 
54
+ module ClassMethods
55
+ RodaPlugins.opt_attr_reader(self, :capture_erb_returns)
56
+ end
57
+
54
58
  module InstanceMethods
55
59
  # Temporarily replace the ERB output buffer
56
60
  # with an empty string, and then yield to the block.
@@ -71,7 +75,7 @@ class Roda
71
75
  if buf_was.respond_to?(:capture) && !buf_was.instance_of?(String)
72
76
  buf_was.capture(&block)
73
77
  else
74
- returns = opts.fetch(:returns) { self.opts[:capture_erb_returns] }
78
+ returns = opts.fetch(:returns) { self.class.capture_erb_returns }
75
79
 
76
80
  begin
77
81
  instance_variable_set(outvar, String.new)
@@ -162,7 +162,7 @@ class Roda
162
162
  app.opts[:chunk_by_default] = opts[:chunk_by_default]
163
163
  app.opts[:force_chunked_encoding] = opts[:force_chunked_encoding]
164
164
  if opts[:headers]
165
- app.opts[:chunk_headers] = (app.opts[:chunk_headers] || {}).merge(opts[:headers]).freeze
165
+ app.opts[:chunk_headers] = (app.chunk_headers || {}).merge(opts[:headers]).freeze
166
166
  end
167
167
  end
168
168
 
@@ -207,6 +207,12 @@ class Roda
207
207
  end
208
208
  end
209
209
 
210
+ module ClassMethods
211
+ RodaPlugins.opt_attr_reader(self, :chunk_by_default, name: :chunk_by_default?)
212
+ RodaPlugins.opt_attr_reader(self, :force_chunked_encoding, name: :force_chunked_encoding?)
213
+ RodaPlugins.opt_attr_reader(self, :chunk_headers)
214
+ end
215
+
210
216
  module InstanceMethods
211
217
  # Disable chunking for the current request. Mostly useful when
212
218
  # chunking is turned on by default.
@@ -217,7 +223,7 @@ class Roda
217
223
  # If chunking by default, call chunked if it hasn't yet been
218
224
  # called and chunking is not specifically disabled.
219
225
  def view(*a)
220
- if opts[:chunk_by_default] && @_chunked != false && !defined?(yield)
226
+ if self.class.chunk_by_default? && @_chunked != false && !defined?(yield)
221
227
  chunked(*a)
222
228
  else
223
229
  super
@@ -228,7 +234,7 @@ class Roda
228
234
  # an overview. If a block is given, it is passed to #delay.
229
235
  def chunked(template, opts=OPTS, &block)
230
236
  if @_chunked.nil?
231
- @_chunked = !self.opts[:force_chunked_encoding] || @_request.http_version == "HTTP/1.1"
237
+ @_chunked = !self.class.force_chunked_encoding? || @_request.http_version == "HTTP/1.1"
232
238
  end
233
239
 
234
240
  if block
@@ -255,10 +261,10 @@ class Roda
255
261
 
256
262
  res = response
257
263
  headers = res.headers
258
- if chunk_headers = self.opts[:chunk_headers]
264
+ if chunk_headers = self.class.chunk_headers
259
265
  headers.merge!(chunk_headers)
260
266
  end
261
- if self.opts[:force_chunked_encoding]
267
+ if self.class.force_chunked_encoding?
262
268
  res[RodaResponseHeaders::TRANSFER_ENCODING] = 'chunked'
263
269
  body = Body.new(self)
264
270
  else
@@ -60,17 +60,19 @@ class Roda
60
60
  end
61
61
 
62
62
  module ClassMethods
63
+ RodaPlugins.opt_attr_reader(self, :class_level_routes)
64
+
63
65
  # Define routing methods that will store class level routes.
64
66
  [:root, :on, :is, :get, :post, :delete, :head, :options, :link, :patch, :put, :trace, :unlink].each do |request_meth|
65
67
  define_method(request_meth) do |*args, &block|
66
68
  meth = define_roda_method("class_level_routing_#{request_meth}", :any, &block)
67
- opts[:class_level_routes] << [request_meth, args, meth].freeze
69
+ class_level_routes << [request_meth, args, meth].freeze
68
70
  end
69
71
  end
70
72
 
71
73
  # Freeze the class level routes so that there can be no thread safety issues at runtime.
72
74
  def freeze
73
- opts[:class_level_routes].freeze
75
+ class_level_routes.freeze
74
76
  super
75
77
  end
76
78
  end
@@ -93,7 +95,7 @@ class Roda
93
95
  @_response.status = nil
94
96
  result.replace(_roda_handle_route do
95
97
  r = @_request
96
- opts[:class_level_routes].each do |request_meth, args, meth|
98
+ self.class.class_level_routes.each do |request_meth, args, meth|
97
99
  r.instance_variable_set(:@remaining_path, @_original_remaining_path)
98
100
  r.public_send(request_meth, *args) do |*a|
99
101
  send(meth, *a)
@@ -93,6 +93,8 @@ class Roda
93
93
  end
94
94
 
95
95
  module ClassMethods
96
+ RodaPlugins.opt_attr_reader(self, :class_matchers)
97
+
96
98
  # Set the matcher and block to use for the given class.
97
99
  # The matcher can be a regexp, registered class matcher, or registered symbol
98
100
  # matcher (if using the symbol_matchers plugin).
@@ -119,7 +121,7 @@ class Roda
119
121
 
120
122
  # Freeze the class_matchers hash when freezing the app.
121
123
  def freeze
122
- opts[:class_matchers].freeze
124
+ class_matchers.freeze
123
125
  super
124
126
  end
125
127
  end
@@ -26,8 +26,8 @@ class Roda
26
26
  private_constant :MUTATE_LINE
27
27
 
28
28
  def self.configure(app, logger=nil, opts=OPTS)
29
- app.opts[:common_logger] = logger || app.opts[:common_logger] || $stderr
30
- app.opts[:common_logger_meth] = app.opts[:common_logger].method(opts.fetch(:method){logger.respond_to?(:write) ? :write : :<<})
29
+ common_logger = app.opts[:common_logger] = logger || app.opts[:common_logger] || $stderr # RODA4: drop setting of option, no longer used
30
+ app.opts[:common_logger_meth] = common_logger.method(opts.fetch(:method){logger.respond_to?(:write) ? :write : :<<})
31
31
  end
32
32
 
33
33
  if RUBY_VERSION >= '2.1'
@@ -36,11 +36,15 @@ class Roda
36
36
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
37
37
  end
38
38
  else
39
- # :nocov:
39
+ # simplecov:disable
40
40
  def self.start_timer # :nodoc:
41
41
  Time.now
42
42
  end
43
- # :nocov:
43
+ # simplecov:enable
44
+ end
45
+
46
+ module ClassMethods
47
+ RodaPlugins.opt_attr_reader(self, :common_logger_meth)
44
48
  end
45
49
 
46
50
  module InstanceMethods
@@ -61,13 +65,13 @@ class Roda
61
65
  line = "#{env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-"} - #{env["REMOTE_USER"] || "-"} [#{Time.now.strftime("%d/%b/%Y:%H:%M:%S %z")}] \"#{env["REQUEST_METHOD"]} #{env["SCRIPT_NAME"]}#{env["PATH_INFO"]}#{"?#{env["QUERY_STRING"]}" if ((qs = env["QUERY_STRING"]) && !qs.empty?)} #{@_request.http_version}\" #{status} #{((length = headers[RodaResponseHeaders::CONTENT_LENGTH]) && (length unless length == '0')) || '-'} #{elapsed_time} "
62
66
  if MUTATE_LINE
63
67
  line.gsub!(/[^[:print:]]/){|c| sprintf("\\x%x", c.ord)}
64
- # :nocov:
68
+ # simplecov:disable
65
69
  else
66
70
  line = line.gsub(/[^[:print:]]/){|c| sprintf("\\x%x", c.ord)}
67
- # :nocov:
71
+ # simplecov:enable
68
72
  end
69
73
  line[-1] = "\n"
70
- opts[:common_logger_meth].call(line)
74
+ self.class.common_logger_meth.call(line)
71
75
  end
72
76
 
73
77
  # Create timer instance used for timing
@@ -23,7 +23,7 @@ class Roda
23
23
  # a request method for whether sessions are allowed.
24
24
  def self.load_dependencies(app, opts=OPTS, &block)
25
25
  app.plugin :sessions, opts
26
- app::RodaRequest.class_eval do
26
+ app::RodaRequest.class_exec do
27
27
  define_method(:use_sessions?, &block)
28
28
  alias use_sessions? use_sessions?
29
29
  end
@@ -69,13 +69,17 @@ class Roda
69
69
  app.opts[:append_content_for] = opts.fetch(:append, true)
70
70
  end
71
71
 
72
+ module ClassMethods
73
+ RodaPlugins.opt_attr_reader(self, :append_content_for, name: :append_content_for?)
74
+ end
75
+
72
76
  module InstanceMethods
73
77
  # If called with a block, store content enclosed by block
74
78
  # under the given key. If called without a block, retrieve
75
79
  # stored content with the given key, or return nil if there
76
80
  # is no content stored with that key.
77
81
  def content_for(key, value=nil, &block)
78
- append = opts[:append_content_for]
82
+ append = self.class.append_content_for?
79
83
 
80
84
  if block || value
81
85
  if block