roda 3.87.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: bee7ae126ad0e6e8081bba94be0ad60e2a5f7523c483b2441571549b1bd29274
4
- data.tar.gz: f5441e96617877d1347f4a5b6e54e5d48713ffc91a8ad3b9282781c0c26ca108
3
+ metadata.gz: bfe23e68b3616d0513c55ca0d6006639eaa5595c36e48445e3a4b961f17dc483
4
+ data.tar.gz: 110d2f273b1b2eee5d0717d7238c3c131d7dd9fd2f7eb5a7ff0b575ce0256af0
5
5
  SHA512:
6
- metadata.gz: 141408b4d85e7467c0b753c05180201b1a63a3096f713241b94d379bad8d65224d96f1d60978ac336a7d8b9fb3093f35c1d2887d422276cf255cd1c64c557719
7
- data.tar.gz: a64207e6d0a46d22c59bc0acc9d56ed43f4dbc75a9d22737908097564a2e4e801f3c19fd3bf95a28722dbf4a828d9930146dd088e421847709948d3a8f2cf24b
6
+ metadata.gz: 2c60bdca82724f3f1f468ca408e6d58a7fb61bb49e5819835afdb59743b82a8f1ad77e98775f8ce720bcc4aff4d72ae2e3526419cba509d60588eefd585aedba
7
+ data.tar.gz: 223ed6296acb9829902050161f88356e4bd6d2e5da5dc06e43175b4a59cd38c312a63c79cd93b5f2c648d7bf25e84f66a1ab02c72b603b42866ad434851350d1
@@ -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
@@ -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 = 87
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.87.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-12-17 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: []
@@ -313,7 +311,6 @@ metadata:
313
311
  documentation_uri: https://roda.jeremyevans.net/documentation.html
314
312
  mailing_list_uri: https://github.com/jeremyevans/roda/discussions
315
313
  source_code_uri: https://github.com/jeremyevans/roda
316
- post_install_message:
317
314
  rdoc_options: []
318
315
  require_paths:
319
316
  - lib
@@ -328,8 +325,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
328
325
  - !ruby/object:Gem::Version
329
326
  version: '0'
330
327
  requirements: []
331
- rubygems_version: 3.5.22
332
- signing_key:
328
+ rubygems_version: 3.6.2
333
329
  specification_version: 4
334
330
  summary: Routing tree web toolkit
335
331
  test_files: []