roda 3.87.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: 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: []