roda 3.86.0 → 3.88.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: 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: []