roda 3.87.0 → 3.89.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: bee7ae126ad0e6e8081bba94be0ad60e2a5f7523c483b2441571549b1bd29274
4
- data.tar.gz: f5441e96617877d1347f4a5b6e54e5d48713ffc91a8ad3b9282781c0c26ca108
3
+ metadata.gz: 5c056e15afde950dcb27063296ba5b911fe3b900d16c121dd8833374731cdbaa
4
+ data.tar.gz: 95225a9c11b07979b50359df7451283eff570557c926f82c49cc34b67d1868fb
5
5
  SHA512:
6
- metadata.gz: 141408b4d85e7467c0b753c05180201b1a63a3096f713241b94d379bad8d65224d96f1d60978ac336a7d8b9fb3093f35c1d2887d422276cf255cd1c64c557719
7
- data.tar.gz: a64207e6d0a46d22c59bc0acc9d56ed43f4dbc75a9d22737908097564a2e4e801f3c19fd3bf95a28722dbf4a828d9930146dd088e421847709948d3a8f2cf24b
6
+ metadata.gz: e38354627d456f2b2bd593703d4ef64f5e934c071d439f1d9647cc8ef010de5fb463d001068ce2e06fd57b0cbccd926005d199b76761ebadefeda699937f5501
7
+ data.tar.gz: d542a024a5dd63bfa069baa38d2929bc0925d410641e303839f4a408139614249f223c3054f1f827550522aa21c0f52429184d3484001f7ed6996bbb9b366add
@@ -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
@@ -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
@@ -151,6 +151,9 @@ class Roda
151
151
  mail
152
152
  end
153
153
  end
154
+ # :nocov:
155
+ ruby2_keywords(:mail) if respond_to?(:ruby2_keywords, true)
156
+ # :nocov:
154
157
 
155
158
  # Calls +mail+ with given arguments and immediately sends the resulting mail.
156
159
  def sendmail(*args)
@@ -158,6 +161,9 @@ class Roda
158
161
  m.deliver
159
162
  end
160
163
  end
164
+ # :nocov:
165
+ ruby2_keywords(:sendmail) if respond_to?(:ruby2_keywords, true)
166
+ # :nocov:
161
167
  end
162
168
 
163
169
  module RequestMethods
@@ -0,0 +1,76 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The part plugin adds a part method, which is a
7
+ # render-like method that treats all keywords as locals.
8
+ #
9
+ # # Can replace this:
10
+ # render(:template, locals: {foo: 'bar'})
11
+ #
12
+ # # With this:
13
+ # part(:template, foo: 'bar')
14
+ #
15
+ # On Ruby 2.7+, the part method takes a keyword splat, so you
16
+ # must pass keywords and not a positional hash for the locals.
17
+ #
18
+ # If you are using the :assume_fixed_locals render plugin option,
19
+ # template caching is enabled, you are using Ruby 3+, and you
20
+ # are freezing your Roda application, in addition to providing a
21
+ # simpler API, this also provides a significant performance
22
+ # improvement (more significant on Ruby 3.4+).
23
+ module Part
24
+ def self.load_dependencies(app)
25
+ app.plugin :render
26
+ end
27
+
28
+ module ClassMethods
29
+ # When freezing, optimize the part method if assuming fixed locals
30
+ # and caching templates.
31
+ def freeze
32
+ if render_opts[:assume_fixed_locals] && !render_opts[:check_template_mtime]
33
+ include AssumeFixedLocalsInstanceMethods
34
+ end
35
+
36
+ super
37
+ end
38
+ end
39
+
40
+ module InstanceMethods
41
+ if RUBY_VERSION >= '2.7'
42
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
43
+ def part(template, **locals, &block)
44
+ render(template, :locals=>locals, &block)
45
+ end
46
+ RUBY
47
+ # :nocov:
48
+ else
49
+ def part(template, locals=OPTS, &block)
50
+ render(template, :locals=>locals, &block)
51
+ end
52
+ end
53
+ # :nocov:
54
+ end
55
+
56
+ module AssumeFixedLocalsInstanceMethods
57
+ # :nocov:
58
+ if RUBY_VERSION >= '3.0'
59
+ # :nocov:
60
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
61
+ def part(template, ...)
62
+ if optimized_method = _optimized_render_method_for_locals(template, OPTS)
63
+ send(optimized_method[0], ...)
64
+ else
65
+ super
66
+ end
67
+ end
68
+ RUBY
69
+ end
70
+ end
71
+ end
72
+
73
+ register_plugin(:part, Part)
74
+ end
75
+ end
76
+
@@ -48,6 +48,9 @@ class Roda
48
48
  #
49
49
  # :allowed_paths :: Set the template paths to allow. Attempts to render paths outside
50
50
  # of these paths will raise an error. Defaults to the +:views+ directory.
51
+ # :assume_fixed_locals :: Set if you are sure all templates in your application use fixed locals
52
+ # to allow for additional optimization. This is ignored unless both
53
+ # compiled methods and fixed locals are not supported.
51
54
  # :cache :: nil/false to explicitly disable permanent template caching. By default, permanent
52
55
  # template caching is disabled by default if RACK_ENV is development. When permanent
53
56
  # template caching is disabled, for templates with paths in the file system, the
@@ -118,6 +121,50 @@ class Roda
118
121
  # have either +:template+, +:inline+, +:path+, or +:content+ (for +view+) as
119
122
  # one of the keys.
120
123
  #
124
+ # = Fixed Locals in Templates
125
+ #
126
+ # By default, you can pass any local variables to any templates. A separate
127
+ # template method is compiled for each combination of locals. This causes
128
+ # multiple issues:
129
+ #
130
+ # * It is inefficient, especially for large templates that are called with
131
+ # many combinations of locals.
132
+ # * It hides issues if unused local variable names are passed to the template
133
+ # * It does not support default values for local variables
134
+ # * It does not support required local variables
135
+ # * It does not support cases where you want to pass values via a keyword splat
136
+ # * It does not support named blocks
137
+ #
138
+ # If you are using Tilt 2.6+, you can used fixed locals in templates, by
139
+ # passing the appropriate options in :template_opts. For example, if you
140
+ # are using ERB templates, the recommended way to use the render plugin is to
141
+ # use the +:extract_fixed_locals+ and +:default_fixed_locals+ template options:
142
+ #
143
+ # plugin :render, template_opts: {extract_fixed_locals: true, default_fixed_locals: '()'}
144
+ #
145
+ # This will default templates to not allowing any local variables to be passed.
146
+ # If the template requires local variables, you can specify them using a magic
147
+ # comment in the template, such as:
148
+ #
149
+ # <%# locals(required_local:, optional_local: nil) %>
150
+ #
151
+ # The magic comment is used as method parameters when defining the compiled template
152
+ # method.
153
+ #
154
+ # For better debugging of issues with invalid keywords being passed to templates that
155
+ # have not been updated to support fixed locals, it can be helpful to set
156
+ # +:default_fixed_locals+ to use a single optional keyword argument
157
+ # <tt>'(_no_kw: nil)'</tt>. This makes the error message show which keywords
158
+ # were passed, instead of showing that the takes no arguments (if you use <tt>'()'</tt>),
159
+ # or that no keywords are accepted (if you pass <tt>(**nil)</tt>).
160
+ #
161
+ # If you are sure your application works with all templates using fixed locals,
162
+ # set the :assume_fixed_locals render plugin option, which will allow the plugin
163
+ # to optimize cache lookup for renders with locals, and avoid duplicate compiled
164
+ # methods for templates rendered both with and without locals.
165
+ #
166
+ # See Tilt's documentation for more information regarding fixed locals.
167
+ #
121
168
  # = Speeding Up Template Rendering
122
169
  #
123
170
  # The render/view method calls are optimized for usage with a single symbol/string
@@ -131,6 +178,23 @@ class Roda
131
178
  # the hash, making sure the +:cache_key+ is unique to the template you are
132
179
  # rendering.
133
180
  #
181
+ # = Recommended +template_opts+
182
+ #
183
+ # Here are the recommended values of :template_opts for new applications (a couple
184
+ # are Erubi-specific and can be ignored if you are using other templates engines):
185
+ #
186
+ # plugin :render,
187
+ # assume_fixed_locals: true, # Optimize plugin by assuming all templates use fixed locals
188
+ # template_opts: {
189
+ # scope_class: self, # Always uses current class as scope class for compiled templates
190
+ # freeze: true, # Freeze string literals in templates
191
+ # extract_fixed_locals: true, # Support fixed locals in templates
192
+ # default_fixed_locals: '()', # Default to templates not supporting local variables
193
+ # escape: true, # For Erubi templates, escapes <%= by default (use <%== for unescaped
194
+ # chain_appends: true, # For Erubi templates, improves performance
195
+ # skip_compiled_encoding_detection: true, # Unless you need encodings explicitly specified
196
+ # }
197
+ #
134
198
  # = Accepting Template Blocks in Methods
135
199
  #
136
200
  # If you are used to Rails, you may be surprised that this type of template code
@@ -228,6 +292,19 @@ class Roda
228
292
  ([1, -2].include?(((compiled_method_arity = Tilt::Template.instance_method(:compiled_method).arity) rescue false)))
229
293
  NO_CACHE = {:cache=>false}.freeze
230
294
  COMPILED_METHOD_SUPPORT = RUBY_VERSION >= '2.3' && tilt_compiled_method_support && ENV['RODA_RENDER_COMPILED_METHOD_SUPPORT'] != 'no'
295
+ FIXED_LOCALS_COMPILED_METHOD_SUPPORT = COMPILED_METHOD_SUPPORT && Tilt::Template.method_defined?(:fixed_locals?)
296
+
297
+ if FIXED_LOCALS_COMPILED_METHOD_SUPPORT
298
+ def self.tilt_template_fixed_locals?(template)
299
+ template.fixed_locals?
300
+ end
301
+ # :nocov:
302
+ else
303
+ def self.tilt_template_fixed_locals?(template)
304
+ false
305
+ end
306
+ end
307
+ # :nocov:
231
308
 
232
309
  if compiled_method_arity == -2
233
310
  def self.tilt_template_compiled_method(template, locals_keys, scope_class)
@@ -257,6 +334,7 @@ class Roda
257
334
  opts[:allowed_paths] ||= [opts[:views]].freeze
258
335
  opts[:allowed_paths] = opts[:allowed_paths].map{|f| app.expand_path(f, nil)}.uniq.freeze
259
336
  opts[:check_paths] = true unless opts.has_key?(:check_paths)
337
+ opts[:assume_fixed_locals] &&= FIXED_LOCALS_COMPILED_METHOD_SUPPORT
260
338
 
261
339
  unless opts.has_key?(:check_template_mtime)
262
340
  opts[:check_template_mtime] = if opts[:cache] == false || opts[:explicit_cache]
@@ -385,6 +463,11 @@ class Roda
385
463
  end
386
464
 
387
465
  if COMPILED_METHOD_SUPPORT
466
+ # Whether the underlying template uses fixed locals.
467
+ def fixed_locals?
468
+ Render.tilt_template_fixed_locals?(@template)
469
+ end
470
+
388
471
  # Compile a method in the given module with the given name that will
389
472
  # call the compiled template method, updating the compiled template method
390
473
  def define_compiled_method(roda_class, method_name, locals_keys=EMPTY_ARRAY)
@@ -403,6 +486,15 @@ class Roda
403
486
  method_name
404
487
  end
405
488
 
489
+ # Returns an appropriate value for the template method cache.
490
+ def define_compiled_method_cache_value(roda_class, method_name, locals_keys=EMPTY_ARRAY)
491
+ if compiled_method = define_compiled_method(roda_class, method_name, locals_keys)
492
+ [compiled_method, false].freeze
493
+ else
494
+ compiled_method
495
+ end
496
+ end
497
+
406
498
  private
407
499
 
408
500
  # Return the compiled method for the current template object.
@@ -422,7 +514,7 @@ class Roda
422
514
  mod.send(:private, method_name)
423
515
  end
424
516
 
425
- send(method_name, locals, &block)
517
+ _call_optimized_template_method([method_name, Render.tilt_template_fixed_locals?(template)], locals, &block)
426
518
  end
427
519
  end
428
520
  end
@@ -450,6 +542,12 @@ class Roda
450
542
  nil
451
543
  end
452
544
 
545
+ # Optimize _call_optimized_template_method if you know all templates
546
+ # are going to be using fixed locals.
547
+ if render_opts[:assume_fixed_locals] && !render_opts[:check_template_mtime]
548
+ include AssumeFixedLocalsInstanceMethods
549
+ end
550
+
453
551
  super
454
552
  end
455
553
  end
@@ -489,17 +587,17 @@ class Roda
489
587
  def _freeze_layout_method
490
588
  if render_opts[:layout]
491
589
  instance = allocate
590
+ # This needs to be called even if COMPILED_METHOD_SUPPORT is not set,
591
+ # in order for the precompile_templates plugin to work correctly.
492
592
  instance.send(:retrieve_template, instance.send(:view_layout_opts, OPTS))
493
593
 
494
- if COMPILED_METHOD_SUPPORT
495
- if (layout_template = render_opts[:optimize_layout]) && !opts[:render][:optimized_layout_method_created]
594
+ if COMPILED_METHOD_SUPPORT && (layout_template = render_opts[:optimize_layout]) && !opts[:render][:optimized_layout_method_created]
496
595
  instance.send(:retrieve_template, :template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout)
497
596
  layout_method = opts[:render][:template_method_cache][:_roda_layout]
498
597
  define_method(:_layout_method){layout_method}
499
598
  private :_layout_method
500
599
  alias_method(:_layout_method, :_layout_method)
501
600
  opts[:render] = opts[:render].merge(:optimized_layout_method_created=>true)
502
- end
503
601
  end
504
602
  end
505
603
  end
@@ -509,9 +607,9 @@ class Roda
509
607
  # Render the given template. See Render for details.
510
608
  def render(template, opts = (no_opts = true; optimized_template = _cached_template_method(template); OPTS), &block)
511
609
  if optimized_template
512
- send(optimized_template, OPTS, &block)
610
+ _call_optimized_template_method(optimized_template, OPTS, &block)
513
611
  elsif !no_opts && opts.length == 1 && (locals = opts[:locals]) && (optimized_template = _optimized_render_method_for_locals(template, locals))
514
- send(optimized_template, locals, &block)
612
+ _call_optimized_template_method(optimized_template, locals, &block)
515
613
  else
516
614
  opts = render_template_opts(template, opts)
517
615
  retrieve_template(opts).render((opts[:scope]||self), (opts[:locals]||OPTS), &block)
@@ -534,7 +632,7 @@ class Roda
534
632
  # and use it if so. This way avoids the extra conditional and local variable
535
633
  # assignments in the next section.
536
634
  if layout_method = _layout_method
537
- return send(layout_method, OPTS){content}
635
+ return _call_optimized_template_method(layout_method, OPTS){content}
538
636
  end
539
637
 
540
638
  # If we have an optimized template method but no optimized layout method, create the
@@ -543,7 +641,7 @@ class Roda
543
641
  if layout_template = self.class.opts[:render][:optimize_layout]
544
642
  retrieve_template(:template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout)
545
643
  if layout_method = _layout_method
546
- return send(layout_method, OPTS){content}
644
+ return _call_optimized_template_method(layout_method, OPTS){content}
547
645
  end
548
646
  end
549
647
  else
@@ -592,39 +690,54 @@ class Roda
592
690
  # of the template render if the optimized path is used, or nil if the optimized
593
691
  # path is not used and the long method needs to be used.
594
692
  def _optimized_render_method_for_locals(template, locals)
693
+ render_opts = self.render_opts
595
694
  return unless method_cache = render_opts[:template_method_cache]
596
695
 
597
- locals_keys = locals.keys.sort
598
- key = [:_render_locals, template, locals_keys]
599
-
600
- optimized_template = case template
696
+ case template
601
697
  when String, Symbol
602
- _cached_template_method_lookup(method_cache, key)
698
+ if fixed_locals = render_opts[:assume_fixed_locals]
699
+ key = template
700
+ if optimized_template = _cached_template_method_lookup(method_cache, key)
701
+ return optimized_template
702
+ end
703
+ else
704
+ key = [:_render_locals, template]
705
+ if optimized_template = _cached_template_method_lookup(method_cache, key)
706
+ # Fixed locals case
707
+ return optimized_template
708
+ end
709
+
710
+ locals_keys = locals.keys.sort
711
+ key << locals_keys
712
+ if optimized_template = _cached_template_method_lookup(method_cache, key)
713
+ # Regular locals case
714
+ return optimized_template
715
+ end
716
+ end
603
717
  else
604
718
  return
605
719
  end
606
720
 
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}"
721
+ if method_cache_key = _cached_template_method_key(key)
722
+ template_obj = retrieve_template(render_template_opts(template, NO_CACHE))
723
+ unless fixed_locals
724
+ key.pop if fixed_locals = Render.tilt_template_fixed_locals?(template_obj)
725
+ key.freeze
726
+ end
727
+ method_name = :"_roda_template_locals_#{self.class.object_id}_#{method_cache_key}"
614
728
 
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)
729
+ method_cache[method_cache_key] = case template_obj
730
+ when Render::TemplateMtimeWrapper
731
+ template_obj.define_compiled_method_cache_value(self.class, method_name, locals_keys)
732
+ else
733
+ begin
734
+ unbound_method = Render.tilt_template_compiled_method(template_obj, locals_keys, self.class)
735
+ rescue ::NotImplementedError
736
+ false
618
737
  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
738
+ self.class::RodaCompiledTemplates.send(:define_method, method_name, unbound_method)
739
+ self.class::RodaCompiledTemplates.send(:private, method_name)
740
+ [method_name, fixed_locals].freeze
628
741
  end
629
742
  end
630
743
  end
@@ -634,11 +747,47 @@ class Roda
634
747
  # a single argument is passed to view.
635
748
  def _optimized_view_content(template)
636
749
  if optimized_template = _cached_template_method(template)
637
- send(optimized_template, OPTS)
750
+ _call_optimized_template_method(optimized_template, OPTS)
638
751
  elsif template.is_a?(Hash) && template.length == 1
639
752
  template[:content]
640
753
  end
641
754
  end
755
+
756
+ if RUBY_VERSION >= '3'
757
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
758
+ def _call_optimized_template_method((meth, fixed_locals), locals, &block)
759
+ if fixed_locals
760
+ send(meth, **locals, &block)
761
+ else
762
+ send(meth, locals, &block)
763
+ end
764
+ end
765
+ RUBY
766
+ # :nocov:
767
+ elsif RUBY_VERSION >= '2'
768
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
769
+ def _call_optimized_template_method((meth, fixed_locals), locals, &block)
770
+ if fixed_locals
771
+ if locals.empty?
772
+ send(meth, &block)
773
+ else
774
+ send(meth, **locals, &block)
775
+ end
776
+ else
777
+ send(meth, locals, &block)
778
+ end
779
+ end
780
+ RUBY
781
+ else
782
+ # Call the optimized template method. This is designed to be used with the
783
+ # method cache, which caches the method name and whether the method uses
784
+ # fixed locals. Methods with fixed locals need to be called with a keyword
785
+ # splat.
786
+ def _call_optimized_template_method((meth, fixed_locals), locals, &block)
787
+ send(meth, locals, &block)
788
+ end
789
+ end
790
+ # :nocov:
642
791
  else
643
792
  def _cached_template_method(_)
644
793
  nil
@@ -648,10 +797,6 @@ class Roda
648
797
  nil
649
798
  end
650
799
 
651
- def _layout_method
652
- nil
653
- end
654
-
655
800
  def _optimized_render_method_for_locals(_, _)
656
801
  nil
657
802
  end
@@ -661,7 +806,6 @@ class Roda
661
806
  end
662
807
  end
663
808
 
664
-
665
809
  # Convert template options to single hash when rendering templates using render.
666
810
  def render_template_opts(template, opts)
667
811
  parse_template_opts(template, opts)
@@ -772,7 +916,7 @@ class Roda
772
916
 
773
917
  if define_compiled_method
774
918
  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)
919
+ method_cache[method_cache_key] = template.define_compiled_method_cache_value(self.class, method_name)
776
920
  end
777
921
  else
778
922
  template = self.class.create_template(opts, template_opts)
@@ -786,7 +930,7 @@ class Roda
786
930
  method_name = :"_roda_template_#{self.class.object_id}_#{method_cache_key}"
787
931
  self.class::RodaCompiledTemplates.send(:define_method, method_name, unbound_method)
788
932
  self.class::RodaCompiledTemplates.send(:private, method_name)
789
- method_cache[method_cache_key] = method_name
933
+ method_cache[method_cache_key] = [method_name, Render.tilt_template_fixed_locals?(template)].freeze
790
934
  end
791
935
  end
792
936
  end
@@ -835,6 +979,18 @@ class Roda
835
979
  end
836
980
  end
837
981
  end
982
+
983
+ module AssumeFixedLocalsInstanceMethods
984
+ # :nocov:
985
+ if RUBY_VERSION >= '3.0'
986
+ # :nocov:
987
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
988
+ def _call_optimized_template_method((meth,_), locals, &block)
989
+ send(meth, **locals, &block)
990
+ end
991
+ RUBY
992
+ end
993
+ end
838
994
  end
839
995
 
840
996
  register_plugin(:render, Render)
@@ -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
@@ -23,8 +23,11 @@ class Roda
23
23
  # :foo
24
24
  # end
25
25
  module SymbolViews
26
- def self.configure(app)
26
+ def self.load_dependencies(app)
27
27
  app.plugin :custom_block_results
28
+ end
29
+
30
+ def self.configure(app)
28
31
  app.opts[:custom_block_results][Symbol] = :view
29
32
  end
30
33
  end
@@ -133,7 +133,7 @@ class Roda
133
133
  return if @_view_options || @_layout_options
134
134
 
135
135
  if subdir = @_view_subdir
136
- template = [subdir, template]
136
+ template = [subdir, template].freeze
137
137
  end
138
138
 
139
139
  super
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 = 87
7
+ RodaMinorVersion = 89
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.87.0
4
+ version: 3.89.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-12-17 00:00:00.000000000 Z
10
+ date: 2025-02-12 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: []
@@ -255,6 +253,7 @@ files:
255
253
  - lib/roda/plugins/padrino_render.rb
256
254
  - lib/roda/plugins/param_matchers.rb
257
255
  - lib/roda/plugins/params_capturing.rb
256
+ - lib/roda/plugins/part.rb
258
257
  - lib/roda/plugins/partials.rb
259
258
  - lib/roda/plugins/pass.rb
260
259
  - lib/roda/plugins/path.rb
@@ -313,7 +312,6 @@ metadata:
313
312
  documentation_uri: https://roda.jeremyevans.net/documentation.html
314
313
  mailing_list_uri: https://github.com/jeremyevans/roda/discussions
315
314
  source_code_uri: https://github.com/jeremyevans/roda
316
- post_install_message:
317
315
  rdoc_options: []
318
316
  require_paths:
319
317
  - lib
@@ -328,8 +326,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
328
326
  - !ruby/object:Gem::Version
329
327
  version: '0'
330
328
  requirements: []
331
- rubygems_version: 3.5.22
332
- signing_key:
329
+ rubygems_version: 3.6.2
333
330
  specification_version: 4
334
331
  summary: Routing tree web toolkit
335
332
  test_files: []