roda 3.86.0 → 3.88.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: a1851a201539b728f1af90ea1b85b2b33a9026f71986cb65c1aabdd079f653ad
4
- data.tar.gz: f360e0bfeb3442df2fa1f96e28362eee9850c0f54d160bfbfc2b11d62d33ba39
3
+ metadata.gz: bfe23e68b3616d0513c55ca0d6006639eaa5595c36e48445e3a4b961f17dc483
4
+ data.tar.gz: 110d2f273b1b2eee5d0717d7238c3c131d7dd9fd2f7eb5a7ff0b575ce0256af0
5
5
  SHA512:
6
- metadata.gz: d2bef4abc3d5e08ddb5a1c9c27f8b626b631a5951cec7cee062c497074cb7f68e6c8b36e75261cc913a580a87d5111438e8a21488669812c9a3b227bf9609e0b
7
- data.tar.gz: e668a47039e529aa026e21ddfd6346b3e1fa224f432b46085b33242c9551ced88278328f2d2d471b2593f841da0a882d8f85dd2468277484409f679485a219df
6
+ metadata.gz: 2c60bdca82724f3f1f468ca408e6d58a7fb61bb49e5819835afdb59743b82a8f1ad77e98775f8ce720bcc4aff4d72ae2e3526419cba509d60588eefd585aedba
7
+ data.tar.gz: 223ed6296acb9829902050161f88356e4bd6d2e5da5dc06e43175b4a59cd38c312a63c79cd93b5f2c648d7bf25e84f66a1ab02c72b603b42866ad434851350d1
@@ -28,7 +28,16 @@ class Roda
28
28
  #
29
29
  # Note that custom block result handling only occurs if the types
30
30
  # are not handled by Roda itself. You cannot use this to modify
31
- # the handling of nil, false, or string results.
31
+ # the handling of nil, false, or string results. Additionally,
32
+ # if the response body has already been written to before the the
33
+ # route block exits, then the result of the block is ignored,
34
+ # and the related +handle_block_result+ block will not be called
35
+ # (this is standard Roda behavior).
36
+ #
37
+ # The return value of the +handle_block_result+ block is written
38
+ # to the body if the block return value is a String, similar to
39
+ # standard Roda handling of block results. Non-String return
40
+ # values are ignored.
32
41
  module CustomBlockResults
33
42
  def self.configure(app)
34
43
  app.opts[:custom_block_results] ||= {}
@@ -55,7 +64,15 @@ class Roda
55
64
  # to get the block result.
56
65
  def unsupported_block_result(result)
57
66
  roda_class.opts[:custom_block_results].each do |klass, meth|
58
- return scope.send(meth, result) if klass === result
67
+ if klass === result
68
+ result = scope.send(meth, result)
69
+
70
+ if String === result
71
+ return result
72
+ else
73
+ return
74
+ end
75
+ end
59
76
  end
60
77
 
61
78
  super
@@ -45,7 +45,7 @@ class Roda
45
45
  end
46
46
 
47
47
  # yield nothing
48
- def each
48
+ def each(&_)
49
49
  end
50
50
 
51
51
  # this should be called by the Rack server
@@ -51,7 +51,8 @@ class Roda
51
51
 
52
52
  # Match if the given uppercase key is present inside the environment.
53
53
  def match_header(key)
54
- key = key.upcase.tr("-","_")
54
+ key = key.upcase
55
+ key.tr!("-","_")
55
56
  unless key == "CONTENT_TYPE" || key == "CONTENT_LENGTH"
56
57
  key = "HTTP_#{key}"
57
58
  end
@@ -75,8 +76,8 @@ class Roda
75
76
  # Match the submitted user agent to the given pattern, capturing any
76
77
  # regexp match groups.
77
78
  def match_user_agent(pattern)
78
- if (user_agent = @env["HTTP_USER_AGENT"]) && user_agent.to_s =~ pattern
79
- @captures.concat($~[1..-1])
79
+ if (user_agent = @env["HTTP_USER_AGENT"]) && (match = pattern.match(user_agent))
80
+ @captures.concat(match.captures)
80
81
  end
81
82
  end
82
83
  end
@@ -0,0 +1,190 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The host_routing plugin adds support for more routing requests based on
7
+ # the requested host. It also adds predicate methods for checking
8
+ # whether a request was requested with the given host.
9
+ #
10
+ # When loading the plugin, you pass a block, which is used for configuring
11
+ # the plugin. For example, if you want to treat requests to api.example.com
12
+ # or api2.example.com as api requests, and treat other requests as www
13
+ # requests, you could use:
14
+ #
15
+ # plugin :host_routing do |hosts|
16
+ # hosts.to :api, "api.example.com", "api2.example.com"
17
+ # hosts.default :www
18
+ # end
19
+ #
20
+ # With this configuration, in your routing tree, you can call the +r.api+ and
21
+ # +r.www+ methods for dispatching to routing blocks only for those types of
22
+ # requests:
23
+ #
24
+ # route do |r|
25
+ # r.api do
26
+ # # requests to api.example.com or api2.example.com
27
+ # end
28
+ #
29
+ # r.www do
30
+ # # requests to other domains
31
+ # end
32
+ # end
33
+ #
34
+ # In addition to the routing methods, predicate methods are also added to the
35
+ # request object:
36
+ #
37
+ # route do |r|
38
+ # "#{r.api?}-#{r.www?}"
39
+ # end
40
+ # # Requests to api.example.com or api2.example.com return "true-false"
41
+ # # Other requests return "false-true"
42
+ #
43
+ # If the +:scope_predicates+ plugin option is given, predicate methods are also
44
+ # created in route block scope:
45
+ #
46
+ # plugin :host_routing, scope_predicates: true do |hosts|
47
+ # hosts.to :api, "api.example.com"
48
+ # hosts.default :www
49
+ # end
50
+ #
51
+ # route do |r|
52
+ # "#{api?}-#{www?}"
53
+ # end
54
+ #
55
+ # To handle hosts that match a certain format (such as all subdomains),
56
+ # where the specific host names are not known up front, you can provide a block
57
+ # when calling +hosts.default+. This block is passed the host name, or an empty
58
+ # string if no host name is provided, and is evaluated in route block scope.
59
+ # When using this support, you should also call +hosts.register+
60
+ # to register host types that could be returned by the block. For example, to
61
+ # handle api subdomains differently:
62
+ #
63
+ # plugin :host_routing do |hosts|
64
+ # hosts.to :api, "api.example.com"
65
+ # hosts.register :api_sub
66
+ # hosts.default :www do |host|
67
+ # :api_sub if host.end_with?(".api.example.com")
68
+ # end
69
+ # end
70
+ #
71
+ # This plugin uses the host method on the request to get the hostname (this method
72
+ # is defined by Rack).
73
+ module HostRouting
74
+ # Setup the host routing support. The block yields an object used to
75
+ # configure the plugin. Options:
76
+ #
77
+ # :scope_predicates :: Setup predicate methods in route block scope
78
+ # in addition to request scope.
79
+ def self.configure(app, opts=OPTS, &block)
80
+ hosts, host_hash, default_block, default_host = DSL.new.process(&block)
81
+ app.opts[:host_routing_hash] = host_hash
82
+ app.opts[:host_routing_default_host] = default_host
83
+
84
+ app.send(:define_method, :_host_routing_default, &default_block) if default_block
85
+
86
+ app::RodaRequest.class_exec do
87
+ hosts.each do |host|
88
+ host_sym = host.to_sym
89
+ define_method(host_sym){|&blk| always(&blk) if _host_routing_host == host}
90
+ alias_method host_sym, host_sym
91
+
92
+ meth = :"#{host}?"
93
+ define_method(meth){_host_routing_host == host}
94
+ alias_method meth, meth
95
+ end
96
+ end
97
+
98
+ if opts[:scope_predicates]
99
+ app.class_exec do
100
+ hosts.each do |host|
101
+ meth = :"#{host}?"
102
+ define_method(meth){@_request.send(meth)}
103
+ alias_method meth, meth
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ class DSL
110
+ def initialize
111
+ @hosts = []
112
+ @host_hash = {}
113
+ end
114
+
115
+ # Run the DSL for the given block.
116
+ def process(&block)
117
+ instance_exec(self, &block)
118
+
119
+ if !@default_host
120
+ raise RodaError, "must call default method inside host_routing plugin block to set default host"
121
+ end
122
+
123
+ @hosts.concat(@host_hash.values)
124
+ @hosts << @default_host
125
+ @hosts.uniq!
126
+ [@hosts.freeze, @host_hash.freeze, @default_block, @default_host].freeze
127
+ end
128
+
129
+ # Register hosts that can be returned. This is only needed if
130
+ # calling register with a block, where the block can return
131
+ # a value that doesn't match a host given to +to+ or +default+.
132
+ def register(*hosts)
133
+ @hosts = hosts
134
+ end
135
+
136
+ # Treat all given hostnames as routing to the give host.
137
+ def to(host, *hostnames)
138
+ hostnames.each do |hostname|
139
+ @host_hash[hostname] = host
140
+ end
141
+ end
142
+
143
+ # Register the default hostname. If a block is provided, it is
144
+ # called with the host if there is no match for one of the hostnames
145
+ # provided to +to+. If the block returns nil/false, the hostname
146
+ # given to this method is used.
147
+ def default(hostname, &block)
148
+ @default_host = hostname
149
+ @default_block = block
150
+ end
151
+ end
152
+ private_constant :DSL
153
+
154
+ module InstanceMethods
155
+ # Handle case where plugin is used without providing a block to
156
+ # +hosts.default+. This returns nil, ensuring that the hostname
157
+ # provided to +hosts.default+ will be used.
158
+ def _host_routing_default(_)
159
+ nil
160
+ end
161
+ end
162
+
163
+ module RequestMethods
164
+ private
165
+
166
+ # Cache the host to use in the host routing support, so the processing
167
+ # is only done once per request.
168
+ def _host_routing_host
169
+ @_host_routing_host ||= _get_host_routing_host
170
+ end
171
+
172
+ # Determine the host to use for the host routing support. Tries the
173
+ # following, in order:
174
+ #
175
+ # * An exact match for a hostname given in +hosts.to+
176
+ # * The return value of the +hosts.default+ block, if given
177
+ # * The default value provided in the +hosts.default+ call
178
+ def _get_host_routing_host
179
+ host = self.host || ""
180
+
181
+ roda_class.opts[:host_routing_hash][host] ||
182
+ scope._host_routing_default(host) ||
183
+ roda_class.opts[:host_routing_default_host]
184
+ end
185
+ end
186
+ end
187
+
188
+ register_plugin(:host_routing, HostRouting)
189
+ end
190
+ end
@@ -53,27 +53,28 @@ class Roda
53
53
  # parse the request body as JSON. Ignore an empty request body.
54
54
  def POST
55
55
  env = @env
56
- if post_params = (env["roda.json_params"] || env["rack.request.form_hash"])
57
- post_params
58
- elsif (input = env["rack.input"]) && content_type =~ /json/
59
- str = _read_json_input(input)
60
- return super if str.empty?
61
- begin
62
- json_params = parse_json(str)
63
- rescue
64
- roda_class.opts[:json_parser_error_handler].call(self)
65
- end
56
+ if post_params = env["roda.json_params"]
57
+ return post_params
58
+ end
59
+
60
+ unless (input = env["rack.input"]) && (content_type = self.content_type) && content_type.include?('json')
61
+ return super
62
+ end
63
+
64
+ str = _read_json_input(input)
65
+ return super if str.empty?
66
+ begin
67
+ json_params = parse_json(str)
68
+ rescue
69
+ roda_class.opts[:json_parser_error_handler].call(self)
70
+ end
66
71
 
67
- wrap = roda_class.opts[:json_parser_wrap]
68
- if wrap == :always || (wrap == :unless_hash && !json_params.is_a?(Hash))
69
- json_params = {"_json"=>json_params}
70
- end
71
- env["roda.json_params"] = json_params
72
- env["rack.request.form_input"] = input
73
- json_params
74
- else
75
- super
72
+ wrap = roda_class.opts[:json_parser_wrap]
73
+ if wrap == :always || (wrap == :unless_hash && !json_params.is_a?(Hash))
74
+ json_params = {"_json"=>json_params}
76
75
  end
76
+ env["roda.json_params"] = json_params
77
+ json_params
77
78
  end
78
79
 
79
80
  private
@@ -118,6 +118,45 @@ class Roda
118
118
  # have either +:template+, +:inline+, +:path+, or +:content+ (for +view+) as
119
119
  # one of the keys.
120
120
  #
121
+ # = Fixed Locals in Templates
122
+ #
123
+ # By default, you can pass any local variables to any templates. A separate
124
+ # template method is compiled for each combination of locals. This causes
125
+ # multiple issues:
126
+ #
127
+ # * It is inefficient, especially for large templates that are called with
128
+ # many combinations of locals.
129
+ # * It hides issues if unused local variable names are passed to the template
130
+ # * It does not support default values for local variables
131
+ # * It does not support required local variables
132
+ # * It does not support cases where you want to pass values via a keyword splat
133
+ # * It does not support named blocks
134
+ #
135
+ # If you are using Tilt 2.6+, you can used fixed locals in templates, by
136
+ # passing the appropriate options in :template_opts. For example, if you
137
+ # are using ERB templates, the recommended way to use the render plugin is to
138
+ # use the +:extract_fixed_locals+ and +:default_fixed_locals+ template options:
139
+ #
140
+ # plugin :render, template_opts: {extract_fixed_locals: true, default_fixed_locals: '()'}
141
+ #
142
+ # This will default templates to not allowing any local variables to be passed.
143
+ # If the template requires local variables, you can specify them using a magic
144
+ # comment in the template, such as:
145
+ #
146
+ # <%# locals(required_local:, optional_local: nil) %>
147
+ #
148
+ # The magic comment is used as method parameters when defining the compiled template
149
+ # method.
150
+ #
151
+ # For better debugging of issues with invalid keywords being passed to templates that
152
+ # have not been updated to support fixed locals, it can be helpful to set
153
+ # +:default_fixed_locals+ to use a single optional keyword argument
154
+ # <tt>'(_no_kw: nil)'</tt>. This makes the error message show which keywords
155
+ # were passed, instead of showing that the takes no arguments (if you use <tt>'()'</tt>),
156
+ # or that no keywords are accepted (if you pass <tt>(**nil)</tt>).
157
+ #
158
+ # See Tilt's documentation for more information regarding fixed locals.
159
+ #
121
160
  # = Speeding Up Template Rendering
122
161
  #
123
162
  # The render/view method calls are optimized for usage with a single symbol/string
@@ -131,6 +170,21 @@ class Roda
131
170
  # the hash, making sure the +:cache_key+ is unique to the template you are
132
171
  # rendering.
133
172
  #
173
+ # = Recommended +template_opts+
174
+ #
175
+ # Here are the recommended values of :template_opts for new applications (a couple
176
+ # are Erubi-specific and can be ignored if you are using other templates engines):
177
+ #
178
+ # plugin :render, template_opts: {
179
+ # scope_class: self, # Always uses current class as scope class for compiled templates
180
+ # freeze: true, # Freeze string literals in templates
181
+ # extract_fixed_locals: true, # Support fixed locals in templates
182
+ # default_fixed_locals: '()', # Default to templates not supporting local variables
183
+ # escape: true, # For Erubi templates, escapes <%= by default (use <%== for unescaped
184
+ # chain_appends: true, # For Erubi templates, improves performance
185
+ # skip_compiled_encoding_detection: true, # Unless you need encodings explicitly specified
186
+ # }
187
+ #
134
188
  # = Accepting Template Blocks in Methods
135
189
  #
136
190
  # If you are used to Rails, you may be surprised that this type of template code
@@ -228,6 +282,19 @@ class Roda
228
282
  ([1, -2].include?(((compiled_method_arity = Tilt::Template.instance_method(:compiled_method).arity) rescue false)))
229
283
  NO_CACHE = {:cache=>false}.freeze
230
284
  COMPILED_METHOD_SUPPORT = RUBY_VERSION >= '2.3' && tilt_compiled_method_support && ENV['RODA_RENDER_COMPILED_METHOD_SUPPORT'] != 'no'
285
+ FIXED_LOCALS_COMPILED_METHOD_SUPPORT = COMPILED_METHOD_SUPPORT && Tilt::Template.method_defined?(:fixed_locals?)
286
+
287
+ if FIXED_LOCALS_COMPILED_METHOD_SUPPORT
288
+ def self.tilt_template_fixed_locals?(template)
289
+ template.fixed_locals?
290
+ end
291
+ # :nocov:
292
+ else
293
+ def self.tilt_template_fixed_locals?(template)
294
+ false
295
+ end
296
+ end
297
+ # :nocov:
231
298
 
232
299
  if compiled_method_arity == -2
233
300
  def self.tilt_template_compiled_method(template, locals_keys, scope_class)
@@ -385,6 +452,11 @@ class Roda
385
452
  end
386
453
 
387
454
  if COMPILED_METHOD_SUPPORT
455
+ # Whether the underlying template uses fixed locals.
456
+ def fixed_locals?
457
+ Render.tilt_template_fixed_locals?(@template)
458
+ end
459
+
388
460
  # Compile a method in the given module with the given name that will
389
461
  # call the compiled template method, updating the compiled template method
390
462
  def define_compiled_method(roda_class, method_name, locals_keys=EMPTY_ARRAY)
@@ -403,6 +475,15 @@ class Roda
403
475
  method_name
404
476
  end
405
477
 
478
+ # Returns an appropriate value for the template method cache.
479
+ def define_compiled_method_cache_value(roda_class, method_name, locals_keys=EMPTY_ARRAY)
480
+ if compiled_method = define_compiled_method(roda_class, method_name, locals_keys)
481
+ [compiled_method, false].freeze
482
+ else
483
+ compiled_method
484
+ end
485
+ end
486
+
406
487
  private
407
488
 
408
489
  # Return the compiled method for the current template object.
@@ -422,7 +503,7 @@ class Roda
422
503
  mod.send(:private, method_name)
423
504
  end
424
505
 
425
- send(method_name, locals, &block)
506
+ _call_optimized_template_method([method_name, Render.tilt_template_fixed_locals?(template)], locals, &block)
426
507
  end
427
508
  end
428
509
  end
@@ -489,17 +570,17 @@ class Roda
489
570
  def _freeze_layout_method
490
571
  if render_opts[:layout]
491
572
  instance = allocate
573
+ # This needs to be called even if COMPILED_METHOD_SUPPORT is not set,
574
+ # in order for the precompile_templates plugin to work correctly.
492
575
  instance.send(:retrieve_template, instance.send(:view_layout_opts, OPTS))
493
576
 
494
- if COMPILED_METHOD_SUPPORT
495
- if (layout_template = render_opts[:optimize_layout]) && !opts[:render][:optimized_layout_method_created]
577
+ if COMPILED_METHOD_SUPPORT && (layout_template = render_opts[:optimize_layout]) && !opts[:render][:optimized_layout_method_created]
496
578
  instance.send(:retrieve_template, :template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout)
497
579
  layout_method = opts[:render][:template_method_cache][:_roda_layout]
498
580
  define_method(:_layout_method){layout_method}
499
581
  private :_layout_method
500
582
  alias_method(:_layout_method, :_layout_method)
501
583
  opts[:render] = opts[:render].merge(:optimized_layout_method_created=>true)
502
- end
503
584
  end
504
585
  end
505
586
  end
@@ -509,9 +590,9 @@ class Roda
509
590
  # Render the given template. See Render for details.
510
591
  def render(template, opts = (no_opts = true; optimized_template = _cached_template_method(template); OPTS), &block)
511
592
  if optimized_template
512
- send(optimized_template, OPTS, &block)
593
+ _call_optimized_template_method(optimized_template, OPTS, &block)
513
594
  elsif !no_opts && opts.length == 1 && (locals = opts[:locals]) && (optimized_template = _optimized_render_method_for_locals(template, locals))
514
- send(optimized_template, locals, &block)
595
+ _call_optimized_template_method(optimized_template, locals, &block)
515
596
  else
516
597
  opts = render_template_opts(template, opts)
517
598
  retrieve_template(opts).render((opts[:scope]||self), (opts[:locals]||OPTS), &block)
@@ -534,7 +615,7 @@ class Roda
534
615
  # and use it if so. This way avoids the extra conditional and local variable
535
616
  # assignments in the next section.
536
617
  if layout_method = _layout_method
537
- return send(layout_method, OPTS){content}
618
+ return _call_optimized_template_method(layout_method, OPTS){content}
538
619
  end
539
620
 
540
621
  # If we have an optimized template method but no optimized layout method, create the
@@ -543,7 +624,7 @@ class Roda
543
624
  if layout_template = self.class.opts[:render][:optimize_layout]
544
625
  retrieve_template(:template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout)
545
626
  if layout_method = _layout_method
546
- return send(layout_method, OPTS){content}
627
+ return _call_optimized_template_method(layout_method, OPTS){content}
547
628
  end
548
629
  end
549
630
  else
@@ -594,37 +675,43 @@ class Roda
594
675
  def _optimized_render_method_for_locals(template, locals)
595
676
  return unless method_cache = render_opts[:template_method_cache]
596
677
 
597
- locals_keys = locals.keys.sort
598
- key = [:_render_locals, template, locals_keys]
599
-
600
- optimized_template = case template
678
+ case template
601
679
  when String, Symbol
602
- _cached_template_method_lookup(method_cache, key)
680
+ key = [:_render_locals, template]
681
+ if optimized_template = _cached_template_method_lookup(method_cache, key)
682
+ # Fixed locals case
683
+ return optimized_template
684
+ end
685
+
686
+ locals_keys = locals.keys.sort
687
+ key << locals_keys
688
+ if optimized_template = _cached_template_method_lookup(method_cache, key)
689
+ # Regular locals case
690
+ return optimized_template
691
+ end
603
692
  else
604
693
  return
605
694
  end
606
695
 
607
- case optimized_template
608
- when Symbol
609
- optimized_template
610
- else
611
- if method_cache_key = _cached_template_method_key(key)
612
- template_obj = retrieve_template(render_template_opts(template, NO_CACHE))
613
- method_name = :"_roda_template_locals_#{self.class.object_id}_#{method_cache_key}"
696
+ if method_cache_key = _cached_template_method_key(key)
697
+ template_obj = retrieve_template(render_template_opts(template, NO_CACHE))
698
+ key.pop if fixed_locals = Render.tilt_template_fixed_locals?(template_obj)
699
+ key.freeze
700
+ method_cache_key.freeze
701
+ method_name = :"_roda_template_locals_#{self.class.object_id}_#{method_cache_key}"
614
702
 
615
- method_cache[method_cache_key] = case template_obj
616
- when Render::TemplateMtimeWrapper
617
- template_obj.define_compiled_method(self.class, method_name, locals_keys)
703
+ method_cache[method_cache_key] = case template_obj
704
+ when Render::TemplateMtimeWrapper
705
+ template_obj.define_compiled_method_cache_value(self.class, method_name, locals_keys)
706
+ else
707
+ begin
708
+ unbound_method = Render.tilt_template_compiled_method(template_obj, locals_keys, self.class)
709
+ rescue ::NotImplementedError
710
+ false
618
711
  else
619
- begin
620
- unbound_method = Render.tilt_template_compiled_method(template_obj, locals_keys, self.class)
621
- rescue ::NotImplementedError
622
- false
623
- else
624
- self.class::RodaCompiledTemplates.send(:define_method, method_name, unbound_method)
625
- self.class::RodaCompiledTemplates.send(:private, method_name)
626
- method_name
627
- end
712
+ self.class::RodaCompiledTemplates.send(:define_method, method_name, unbound_method)
713
+ self.class::RodaCompiledTemplates.send(:private, method_name)
714
+ [method_name, fixed_locals].freeze
628
715
  end
629
716
  end
630
717
  end
@@ -634,11 +721,47 @@ class Roda
634
721
  # a single argument is passed to view.
635
722
  def _optimized_view_content(template)
636
723
  if optimized_template = _cached_template_method(template)
637
- send(optimized_template, OPTS)
724
+ _call_optimized_template_method(optimized_template, OPTS)
638
725
  elsif template.is_a?(Hash) && template.length == 1
639
726
  template[:content]
640
727
  end
641
728
  end
729
+
730
+ if RUBY_VERSION >= '3'
731
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
732
+ def _call_optimized_template_method((meth, fixed_locals), locals, &block)
733
+ if fixed_locals
734
+ send(meth, **locals, &block)
735
+ else
736
+ send(meth, locals, &block)
737
+ end
738
+ end
739
+ RUBY
740
+ # :nocov:
741
+ elsif RUBY_VERSION >= '2'
742
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
743
+ def _call_optimized_template_method((meth, fixed_locals), locals, &block)
744
+ if fixed_locals
745
+ if locals.empty?
746
+ send(meth, &block)
747
+ else
748
+ send(meth, **locals, &block)
749
+ end
750
+ else
751
+ send(meth, locals, &block)
752
+ end
753
+ end
754
+ RUBY
755
+ else
756
+ # Call the optimized template method. This is designed to be used with the
757
+ # method cache, which caches the method name and whether the method uses
758
+ # fixed locals. Methods with fixed locals need to be called with a keyword
759
+ # splat.
760
+ def _call_optimized_template_method((meth, fixed_locals), locals, &block)
761
+ send(meth, locals, &block)
762
+ end
763
+ end
764
+ # :nocov:
642
765
  else
643
766
  def _cached_template_method(_)
644
767
  nil
@@ -648,10 +771,6 @@ class Roda
648
771
  nil
649
772
  end
650
773
 
651
- def _layout_method
652
- nil
653
- end
654
-
655
774
  def _optimized_render_method_for_locals(_, _)
656
775
  nil
657
776
  end
@@ -661,7 +780,6 @@ class Roda
661
780
  end
662
781
  end
663
782
 
664
-
665
783
  # Convert template options to single hash when rendering templates using render.
666
784
  def render_template_opts(template, opts)
667
785
  parse_template_opts(template, opts)
@@ -772,7 +890,7 @@ class Roda
772
890
 
773
891
  if define_compiled_method
774
892
  method_name = :"_roda_template_#{self.class.object_id}_#{method_cache_key}"
775
- method_cache[method_cache_key] = template.define_compiled_method(self.class, method_name)
893
+ method_cache[method_cache_key] = template.define_compiled_method_cache_value(self.class, method_name)
776
894
  end
777
895
  else
778
896
  template = self.class.create_template(opts, template_opts)
@@ -786,7 +904,7 @@ class Roda
786
904
  method_name = :"_roda_template_#{self.class.object_id}_#{method_cache_key}"
787
905
  self.class::RodaCompiledTemplates.send(:define_method, method_name, unbound_method)
788
906
  self.class::RodaCompiledTemplates.send(:private, method_name)
789
- method_cache[method_cache_key] = method_name
907
+ method_cache[method_cache_key] = [method_name, Render.tilt_template_fixed_locals?(template)].freeze
790
908
  end
791
909
  end
792
910
  end
@@ -50,17 +50,36 @@ class Roda
50
50
  # Set a compiled path on the created template, if the path for
51
51
  # the template is in one of the allowed_views.
52
52
  def create_template(opts, template_opts)
53
- template = super
54
- return template if opts[:template_block]
53
+ return super if opts[:template_block]
55
54
 
56
55
  path = File.expand_path(opts[:path])
56
+ compiled_path = nil
57
57
  (self.opts[:render_coverage_strip_paths] || render_opts[:allowed_paths]).each do |dir|
58
58
  if path.start_with?(dir + '/')
59
- template.compiled_path = File.join(self.opts[:render_coverage_dir], path[dir.length+1, 10000000].gsub('/', '-'))
59
+ compiled_path = File.join(self.opts[:render_coverage_dir], path[dir.length+1, 10000000].gsub('/', '-'))
60
60
  break
61
61
  end
62
62
  end
63
63
 
64
+ # For Tilt 2.6+, when using :scope_class and fixed locals, must provide
65
+ # compiled path as option, since compilation happens during initalization
66
+ # in that case. This option should be ignored if the template does not
67
+ # support it, but some template class may break if the option is not
68
+ # handled, so for compatibility, only set the method if Tilt::Template
69
+ # will handle it.
70
+ if compiled_path && Tilt::Template.method_defined?(:fixed_locals?)
71
+ template_opts = template_opts.dup
72
+ template_opts[:compiled_path] = compiled_path
73
+ compiled_path = nil
74
+ end
75
+
76
+ template = super
77
+
78
+ # Set compiled path for template when using older tilt versions.
79
+ # :nocov:
80
+ template.compiled_path = compiled_path if compiled_path
81
+ # :nocov:
82
+
64
83
  template
65
84
  end
66
85
  end
@@ -105,7 +105,7 @@ class Roda
105
105
  def _optimized_render_each(enum, optimized_template, as, locals)
106
106
  enum.map do |v|
107
107
  locals[as] = v
108
- send(optimized_template, locals)
108
+ _call_optimized_template_method(optimized_template, locals)
109
109
  end.join
110
110
  end
111
111
  else
data/lib/roda/response.rb CHANGED
@@ -46,8 +46,6 @@ class Roda
46
46
 
47
47
  # Instance methods for RodaResponse
48
48
  module ResponseMethods
49
- DEFAULT_HEADERS = {RodaResponseHeaders::CONTENT_TYPE => "text/html".freeze}.freeze
50
-
51
49
  # The body for the current response.
52
50
  attr_reader :body
53
51
 
@@ -179,11 +177,15 @@ class Roda
179
177
  private
180
178
 
181
179
  if defined?(Rack::Headers) && Rack::Headers.is_a?(Class)
180
+ DEFAULT_HEADERS = Rack::Headers[{RodaResponseHeaders::CONTENT_TYPE => "text/html".freeze}].freeze
181
+
182
182
  # Use Rack::Headers for headers by default on Rack 3
183
183
  def _initialize_headers
184
184
  Rack::Headers.new
185
185
  end
186
186
  else
187
+ DEFAULT_HEADERS = {RodaResponseHeaders::CONTENT_TYPE => "text/html".freeze}.freeze
188
+
187
189
  # Use plain hash for headers by default on Rack 1-2
188
190
  def _initialize_headers
189
191
  {}
data/lib/roda/version.rb CHANGED
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 3
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 86
7
+ RodaMinorVersion = 88
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,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.86.0
4
+ version: 3.88.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-11-12 00:00:00.000000000 Z
10
+ date: 2025-01-14 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rack
@@ -150,7 +149,6 @@ dependencies:
150
149
  - - ">="
151
150
  - !ruby/object:Gem::Version
152
151
  version: '0'
153
- description:
154
152
  email:
155
153
  - code@jeremyevans.net
156
154
  executables: []
@@ -225,6 +223,7 @@ files:
225
223
  - lib/roda/plugins/hmac_paths.rb
226
224
  - lib/roda/plugins/hooks.rb
227
225
  - lib/roda/plugins/host_authorization.rb
226
+ - lib/roda/plugins/host_routing.rb
228
227
  - lib/roda/plugins/hsts.rb
229
228
  - lib/roda/plugins/indifferent_params.rb
230
229
  - lib/roda/plugins/inject_erb.rb
@@ -312,7 +311,6 @@ metadata:
312
311
  documentation_uri: https://roda.jeremyevans.net/documentation.html
313
312
  mailing_list_uri: https://github.com/jeremyevans/roda/discussions
314
313
  source_code_uri: https://github.com/jeremyevans/roda
315
- post_install_message:
316
314
  rdoc_options: []
317
315
  require_paths:
318
316
  - lib
@@ -327,8 +325,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
327
325
  - !ruby/object:Gem::Version
328
326
  version: '0'
329
327
  requirements: []
330
- rubygems_version: 3.5.22
331
- signing_key:
328
+ rubygems_version: 3.6.2
332
329
  specification_version: 4
333
330
  summary: Routing tree web toolkit
334
331
  test_files: []