actionview 6.1.4.4 → 7.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +93 -297
  3. data/MIT-LICENSE +1 -1
  4. data/lib/action_view/base.rb +3 -3
  5. data/lib/action_view/buffers.rb +2 -2
  6. data/lib/action_view/cache_expiry.rb +46 -32
  7. data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
  8. data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
  9. data/lib/action_view/dependency_tracker.rb +6 -147
  10. data/lib/action_view/digestor.rb +7 -4
  11. data/lib/action_view/flows.rb +4 -4
  12. data/lib/action_view/gem_version.rb +4 -4
  13. data/lib/action_view/helpers/active_model_helper.rb +1 -1
  14. data/lib/action_view/helpers/asset_tag_helper.rb +84 -29
  15. data/lib/action_view/helpers/asset_url_helper.rb +7 -7
  16. data/lib/action_view/helpers/atom_feed_helper.rb +3 -4
  17. data/lib/action_view/helpers/cache_helper.rb +51 -3
  18. data/lib/action_view/helpers/capture_helper.rb +2 -2
  19. data/lib/action_view/helpers/controller_helper.rb +2 -2
  20. data/lib/action_view/helpers/csp_helper.rb +1 -1
  21. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  22. data/lib/action_view/helpers/date_helper.rb +5 -5
  23. data/lib/action_view/helpers/debug_helper.rb +3 -1
  24. data/lib/action_view/helpers/form_helper.rb +72 -12
  25. data/lib/action_view/helpers/form_options_helper.rb +65 -33
  26. data/lib/action_view/helpers/form_tag_helper.rb +73 -30
  27. data/lib/action_view/helpers/javascript_helper.rb +3 -5
  28. data/lib/action_view/helpers/number_helper.rb +3 -4
  29. data/lib/action_view/helpers/output_safety_helper.rb +2 -2
  30. data/lib/action_view/helpers/rendering_helper.rb +1 -1
  31. data/lib/action_view/helpers/sanitize_helper.rb +2 -2
  32. data/lib/action_view/helpers/tag_helper.rb +17 -4
  33. data/lib/action_view/helpers/tags/base.rb +2 -14
  34. data/lib/action_view/helpers/tags/check_box.rb +1 -1
  35. data/lib/action_view/helpers/tags/collection_select.rb +1 -1
  36. data/lib/action_view/helpers/tags/time_field.rb +10 -1
  37. data/lib/action_view/helpers/tags/weekday_select.rb +27 -0
  38. data/lib/action_view/helpers/tags.rb +3 -2
  39. data/lib/action_view/helpers/text_helper.rb +24 -13
  40. data/lib/action_view/helpers/translation_helper.rb +1 -2
  41. data/lib/action_view/helpers/url_helper.rb +110 -81
  42. data/lib/action_view/helpers.rb +25 -25
  43. data/lib/action_view/lookup_context.rb +33 -52
  44. data/lib/action_view/model_naming.rb +1 -1
  45. data/lib/action_view/path_set.rb +16 -22
  46. data/lib/action_view/railtie.rb +15 -2
  47. data/lib/action_view/render_parser.rb +188 -0
  48. data/lib/action_view/renderer/abstract_renderer.rb +2 -2
  49. data/lib/action_view/renderer/partial_renderer.rb +0 -34
  50. data/lib/action_view/renderer/renderer.rb +4 -4
  51. data/lib/action_view/renderer/streaming_template_renderer.rb +3 -3
  52. data/lib/action_view/renderer/template_renderer.rb +6 -2
  53. data/lib/action_view/rendering.rb +2 -2
  54. data/lib/action_view/ripper_ast_parser.rb +198 -0
  55. data/lib/action_view/routing_url_for.rb +1 -1
  56. data/lib/action_view/template/error.rb +108 -13
  57. data/lib/action_view/template/handlers/erb.rb +6 -0
  58. data/lib/action_view/template/handlers.rb +3 -3
  59. data/lib/action_view/template/html.rb +3 -3
  60. data/lib/action_view/template/inline.rb +3 -3
  61. data/lib/action_view/template/raw_file.rb +3 -3
  62. data/lib/action_view/template/resolver.rb +84 -311
  63. data/lib/action_view/template/text.rb +3 -3
  64. data/lib/action_view/template/types.rb +14 -12
  65. data/lib/action_view/template.rb +10 -1
  66. data/lib/action_view/template_details.rb +66 -0
  67. data/lib/action_view/template_path.rb +64 -0
  68. data/lib/action_view/test_case.rb +6 -2
  69. data/lib/action_view/testing/resolvers.rb +11 -12
  70. data/lib/action_view/unbound_template.rb +33 -7
  71. data/lib/action_view.rb +3 -4
  72. metadata +22 -15
@@ -10,32 +10,12 @@ require "concurrent/map"
10
10
  module ActionView
11
11
  # = Action View Resolver
12
12
  class Resolver
13
- # Keeps all information about view path and builds virtual path.
14
- class Path
15
- attr_reader :name, :prefix, :partial, :virtual
16
- alias_method :partial?, :partial
17
-
18
- def self.build(name, prefix, partial)
19
- virtual = +""
20
- virtual << "#{prefix}/" unless prefix.empty?
21
- virtual << (partial ? "_#{name}" : name)
22
- new name, prefix, partial, virtual
23
- end
24
-
25
- def initialize(name, prefix, partial, virtual)
26
- @name = name
27
- @prefix = prefix
28
- @partial = partial
29
- @virtual = virtual
30
- end
31
-
32
- def to_str
33
- @virtual
34
- end
35
- alias :to_s :to_str
36
- end
13
+ Path = ActionView::TemplatePath
14
+ deprecate_constant :Path
37
15
 
38
16
  class PathParser # :nodoc:
17
+ ParsedPath = Struct.new(:path, :details)
18
+
39
19
  def build_path_regex
40
20
  handlers = Template::Handlers.extensions.map { |x| Regexp.escape(x) }.join("|")
41
21
  formats = Template::Types.symbols.map { |x| Regexp.escape(x) }.join("|")
@@ -58,79 +38,15 @@ module ActionView
58
38
  def parse(path)
59
39
  @regex ||= build_path_regex
60
40
  match = @regex.match(path)
61
- {
62
- prefix: match[:prefix] || "",
63
- action: match[:action],
64
- partial: !!match[:partial],
65
- locale: match[:locale]&.to_sym,
66
- handler: match[:handler]&.to_sym,
67
- format: match[:format]&.to_sym,
68
- variant: match[:variant]
69
- }
70
- end
71
- end
72
-
73
- # Threadsafe template cache
74
- class Cache #:nodoc:
75
- class SmallCache < Concurrent::Map
76
- def initialize(options = {})
77
- super(options.merge(initial_capacity: 2))
78
- end
79
- end
80
-
81
- # Preallocate all the default blocks for performance/memory consumption reasons
82
- PARTIAL_BLOCK = lambda { |cache, partial| cache[partial] = SmallCache.new }
83
- PREFIX_BLOCK = lambda { |cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK) }
84
- NAME_BLOCK = lambda { |cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK) }
85
- KEY_BLOCK = lambda { |cache, key| cache[key] = SmallCache.new(&NAME_BLOCK) }
86
-
87
- # Usually a majority of template look ups return nothing, use this canonical preallocated array to save memory
88
- NO_TEMPLATES = [].freeze
89
-
90
- def initialize
91
- @data = SmallCache.new(&KEY_BLOCK)
92
- @query_cache = SmallCache.new
93
- end
94
-
95
- def inspect
96
- "#{to_s[0..-2]} keys=#{@data.size} queries=#{@query_cache.size}>"
97
- end
98
-
99
- # Cache the templates returned by the block
100
- def cache(key, name, prefix, partial, locals)
101
- @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
102
- end
103
-
104
- def cache_query(query) # :nodoc:
105
- @query_cache[query] ||= canonical_no_templates(yield)
106
- end
107
-
108
- def clear
109
- @data.clear
110
- @query_cache.clear
111
- end
112
-
113
- # Get the cache size. Do not call this
114
- # method. This method is not guaranteed to be here ever.
115
- def size # :nodoc:
116
- size = 0
117
- @data.each_value do |v1|
118
- v1.each_value do |v2|
119
- v2.each_value do |v3|
120
- v3.each_value do |v4|
121
- size += v4.size
122
- end
123
- end
124
- end
125
- end
126
-
127
- size + @query_cache.size
41
+ path = TemplatePath.build(match[:action], match[:prefix] || "", !!match[:partial])
42
+ details = TemplateDetails.new(
43
+ match[:locale]&.to_sym,
44
+ match[:handler]&.to_sym,
45
+ match[:format]&.to_sym,
46
+ match[:variant]&.to_sym
47
+ )
48
+ ParsedPath.new(path, details)
128
49
  end
129
-
130
- private
131
- def canonical_no_templates(templates)
132
- templates.empty? ? NO_TEMPLATES : templates
133
- end
134
50
  end
135
51
 
136
52
  cattr_accessor :caching, default: true
@@ -139,25 +55,17 @@ module ActionView
139
55
  alias :caching? :caching
140
56
  end
141
57
 
142
- def initialize
143
- @cache = Cache.new
144
- end
145
-
146
58
  def clear_cache
147
- @cache.clear
148
59
  end
149
60
 
150
61
  # Normalizes the arguments and passes it on to find_templates.
151
62
  def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = [])
152
- locals = locals.map(&:to_s).sort!.freeze
153
-
154
- cached(key, [name, prefix, partial], details, locals) do
155
- _find_all(name, prefix, partial, details, key, locals)
156
- end
63
+ _find_all(name, prefix, partial, details, key, locals)
157
64
  end
158
65
 
159
- def find_all_with_query(query) # :nodoc:
160
- @cache.cache_query(query) { find_template_paths(File.join(@path, query)) }
66
+ def all_template_paths # :nodoc:
67
+ # Not implemented by default
68
+ []
161
69
  end
162
70
 
163
71
  private
@@ -173,34 +81,18 @@ module ActionView
173
81
  def find_templates(name, prefix, partial, details, locals = [])
174
82
  raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, locals = []) method"
175
83
  end
176
-
177
- # Handles templates caching. If a key is given and caching is on
178
- # always check the cache before hitting the resolver. Otherwise,
179
- # it always hits the resolver but if the key is present, check if the
180
- # resolver is fresher before returning it.
181
- def cached(key, path_info, details, locals)
182
- name, prefix, partial = path_info
183
-
184
- if key
185
- @cache.cache(key, name, prefix, partial, locals) do
186
- yield
187
- end
188
- else
189
- yield
190
- end
191
- end
192
84
  end
193
85
 
194
- # An abstract class that implements a Resolver with path semantics.
195
- class PathResolver < Resolver #:nodoc:
196
- EXTENSIONS = { locale: ".", formats: ".", variants: "+", handlers: "." }
197
- DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
86
+ # A resolver that loads files from the filesystem.
87
+ class FileSystemResolver < Resolver
88
+ attr_reader :path
198
89
 
199
- def initialize
200
- @pattern = DEFAULT_PATTERN
90
+ def initialize(path)
91
+ raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
201
92
  @unbound_templates = Concurrent::Map.new
202
93
  @path_parser = PathParser.new
203
- super
94
+ @path = File.expand_path(path)
95
+ super()
204
96
  end
205
97
 
206
98
  def clear_cache
@@ -209,26 +101,37 @@ module ActionView
209
101
  super
210
102
  end
211
103
 
212
- private
213
- def _find_all(name, prefix, partial, details, key, locals)
214
- path = Path.build(name, prefix, partial)
215
- query(path, details, details[:formats], locals, cache: !!key)
104
+ def to_s
105
+ @path.to_s
106
+ end
107
+ alias :to_path :to_s
108
+
109
+ def eql?(resolver)
110
+ self.class.equal?(resolver.class) && to_path == resolver.to_path
111
+ end
112
+ alias :== :eql?
113
+
114
+ def all_template_paths # :nodoc:
115
+ paths = template_glob("**/*")
116
+ paths.map do |filename|
117
+ filename.from(@path.size + 1).remove(/\.[^\/]*\z/)
118
+ end.uniq.map do |filename|
119
+ TemplatePath.parse(filename)
216
120
  end
121
+ end
217
122
 
218
- def query(path, details, formats, locals, cache:)
219
- template_paths = find_template_paths_from_details(path, details)
220
- template_paths = reject_files_external_to_app(template_paths)
123
+ private
124
+ def _find_all(name, prefix, partial, details, key, locals)
125
+ requested_details = key || TemplateDetails::Requested.new(**details)
126
+ cache = key ? @unbound_templates : Concurrent::Map.new
221
127
 
222
- template_paths.map do |template|
223
- unbound_template =
224
- if cache
225
- @unbound_templates.compute_if_absent([template, path.virtual]) do
226
- build_unbound_template(template, path.virtual)
227
- end
228
- else
229
- build_unbound_template(template, path.virtual)
230
- end
128
+ unbound_templates =
129
+ cache.compute_if_absent(TemplatePath.virtual(name, prefix, partial)) do
130
+ path = TemplatePath.build(name, prefix, partial)
131
+ unbound_templates_from_path(path)
132
+ end
231
133
 
134
+ filter_and_sort_by_details(unbound_templates, requested_details).map do |unbound_template|
232
135
  unbound_template.bind_locals(locals)
233
136
  end
234
137
  end
@@ -237,196 +140,66 @@ module ActionView
237
140
  Template::Sources::File.new(template)
238
141
  end
239
142
 
240
- def build_unbound_template(template, virtual_path)
241
- handler, format, variant = extract_handler_and_format_and_variant(template)
143
+ def build_unbound_template(template)
144
+ parsed = @path_parser.parse(template.from(@path.size + 1))
145
+ details = parsed.details
242
146
  source = source_for_template(template)
243
147
 
244
148
  UnboundTemplate.new(
245
149
  source,
246
150
  template,
247
- handler,
248
- virtual_path: virtual_path,
249
- format: format,
250
- variant: variant,
151
+ details: details,
152
+ virtual_path: parsed.path.virtual,
251
153
  )
252
154
  end
253
155
 
254
- def reject_files_external_to_app(files)
255
- files.reject { |filename| !inside_path?(@path, filename) }
256
- end
257
-
258
- def find_template_paths_from_details(path, details)
156
+ def unbound_templates_from_path(path)
259
157
  if path.name.include?(".")
260
- ActiveSupport::Deprecation.warn("Rendering actions with '.' in the name is deprecated: #{path}")
261
- end
262
-
263
- query = build_query(path, details)
264
- find_template_paths(query)
265
- end
266
-
267
- def find_template_paths(query)
268
- Dir[query].uniq.reject do |filename|
269
- File.directory?(filename) ||
270
- # deals with case-insensitive file systems.
271
- !File.fnmatch(query, filename, File::FNM_EXTGLOB)
272
- end
273
- end
274
-
275
- def inside_path?(path, filename)
276
- filename = File.expand_path(filename)
277
- path = File.join(path, "")
278
- filename.start_with?(path)
279
- end
280
-
281
- # Helper for building query glob string based on resolver's pattern.
282
- def build_query(path, details)
283
- query = @pattern.dup
284
-
285
- prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
286
- query.gsub!(/:prefix(\/)?/, prefix)
287
-
288
- partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
289
- query.gsub!(":action", partial)
290
-
291
- details.each do |ext, candidates|
292
- if ext == :variants && candidates == :any
293
- query.gsub!(/:#{ext}/, "*")
294
- else
295
- query.gsub!(/:#{ext}/, "{#{candidates.compact.uniq.join(',')}}")
296
- end
158
+ return []
297
159
  end
298
160
 
299
- File.expand_path(query, @path)
300
- end
301
-
302
- def escape_entry(entry)
303
- entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
304
- end
305
-
306
- # Extract handler, formats and variant from path. If a format cannot be found neither
307
- # from the path, or the handler, we should return the array of formats given
308
- # to the resolver.
309
- def extract_handler_and_format_and_variant(path)
310
- details = @path_parser.parse(path)
311
-
312
- handler = Template.handler_for_extension(details[:handler])
313
- format = details[:format] || handler.try(:default_format)
314
- variant = details[:variant]
315
-
316
- # Template::Types[format] and handler.default_format can return nil
317
- [handler, format, variant]
318
- end
319
- end
320
-
321
- # A resolver that loads files from the filesystem.
322
- class FileSystemResolver < PathResolver
323
- attr_reader :path
324
-
325
- def initialize(path)
326
- raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
327
- super()
328
- @path = File.expand_path(path)
329
- end
330
-
331
- def to_s
332
- @path.to_s
333
- end
334
- alias :to_path :to_s
335
-
336
- def eql?(resolver)
337
- self.class.equal?(resolver.class) && to_path == resolver.to_path
338
- end
339
- alias :== :eql?
340
- end
341
-
342
- # An Optimized resolver for Rails' most common case.
343
- class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
344
- def initialize(path)
345
- super(path)
346
- end
347
-
348
- private
349
- def find_candidate_template_paths(path)
350
161
  # Instead of checking for every possible path, as our other globs would
351
162
  # do, scan the directory for files with the right prefix.
352
- query = "#{escape_entry(File.join(@path, path))}*"
163
+ paths = template_glob("#{escape_entry(path.to_s)}*")
353
164
 
354
- Dir[query].reject do |filename|
355
- File.directory?(filename)
165
+ paths.map do |path|
166
+ build_unbound_template(path)
167
+ end.select do |template|
168
+ # Select for exact virtual path match, including case sensitivity
169
+ template.virtual_path == path.virtual
356
170
  end
357
171
  end
358
172
 
359
- def find_template_paths_from_details(path, details)
360
- if path.name.include?(".")
361
- # Fall back to the unoptimized resolver, which will warn
362
- return super
173
+ def filter_and_sort_by_details(templates, requested_details)
174
+ filtered_templates = templates.select do |template|
175
+ template.details.matches?(requested_details)
363
176
  end
364
177
 
365
- candidates = find_candidate_template_paths(path)
366
-
367
- regex = build_regex(path, details)
368
-
369
- candidates.uniq.reject do |filename|
370
- # This regex match does double duty of finding only files which match
371
- # details (instead of just matching the prefix) and also filtering for
372
- # case-insensitive file systems.
373
- !regex.match?(filename) ||
374
- File.directory?(filename)
375
- end.sort_by do |filename|
376
- # Because we scanned the directory, instead of checking for files
377
- # one-by-one, they will be returned in an arbitrary order.
378
- # We can use the matches found by the regex and sort by their index in
379
- # details.
380
- match = filename.match(regex)
381
- EXTENSIONS.keys.map do |ext|
382
- if ext == :variants && details[ext] == :any
383
- match[ext].nil? ? 0 : 1
384
- elsif match[ext].nil?
385
- # No match should be last
386
- details[ext].length
387
- else
388
- found = match[ext].to_sym
389
- details[ext].index(found)
390
- end
178
+ if filtered_templates.count > 1
179
+ filtered_templates.sort_by! do |template|
180
+ template.details.sort_key_for(requested_details)
391
181
  end
392
182
  end
393
- end
394
183
 
395
- def build_regex(path, details)
396
- query = Regexp.escape(File.join(@path, path))
397
- exts = EXTENSIONS.map do |ext, prefix|
398
- match =
399
- if ext == :variants && details[ext] == :any
400
- ".*?"
401
- else
402
- arr = details[ext].compact
403
- arr.uniq!
404
- arr.map! { |e| Regexp.escape(e) }
405
- arr.join("|")
406
- end
407
- prefix = Regexp.escape(prefix)
408
- "(#{prefix}(?<#{ext}>#{match}))?"
409
- end.join
410
-
411
- %r{\A#{query}#{exts}\z}
184
+ filtered_templates
412
185
  end
413
- end
414
186
 
415
- # The same as FileSystemResolver but does not allow templates to store
416
- # a virtual path since it is invalid for such resolvers.
417
- class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
418
- private_class_method :new
187
+ # Safe glob within @path
188
+ def template_glob(glob)
189
+ query = File.join(escape_entry(@path), glob)
190
+ path_with_slash = File.join(@path, "")
419
191
 
420
- def self.instances
421
- [new(""), new("/")]
422
- end
192
+ Dir.glob(query).filter_map do |filename|
193
+ filename = File.expand_path(filename)
194
+ next if File.directory?(filename)
195
+ next unless filename.start_with?(path_with_slash)
423
196
 
424
- def build_unbound_template(template, _)
425
- super(template, nil)
426
- end
197
+ filename
198
+ end
199
+ end
427
200
 
428
- def reject_files_external_to_app(files)
429
- files
430
- end
201
+ def escape_entry(entry)
202
+ entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
203
+ end
431
204
  end
432
205
  end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActionView #:nodoc:
3
+ module ActionView # :nodoc:
4
4
  # = Action View Text Template
5
- class Template #:nodoc:
6
- class Text #:nodoc:
5
+ class Template # :nodoc:
6
+ class Text # :nodoc:
7
7
  attr_accessor :type
8
8
 
9
9
  def initialize(string)
@@ -3,8 +3,8 @@
3
3
  require "active_support/core_ext/module/attribute_accessors"
4
4
 
5
5
  module ActionView
6
- class Template #:nodoc:
7
- class Types
6
+ class Template # :nodoc:
7
+ module Types
8
8
  class Type
9
9
  SET = Struct.new(:symbols).new([ :html, :text, :js, :css, :xml, :json ])
10
10
 
@@ -37,21 +37,23 @@ module ActionView
37
37
  end
38
38
  end
39
39
 
40
- cattr_accessor :type_klass
40
+ class << self
41
+ attr_accessor :type_klass
41
42
 
42
- def self.delegate_to(klass)
43
- self.type_klass = klass
44
- end
43
+ def delegate_to(klass)
44
+ self.type_klass = klass
45
+ end
45
46
 
46
- delegate_to Type
47
+ def [](type)
48
+ type_klass[type]
49
+ end
47
50
 
48
- def self.[](type)
49
- type_klass[type]
51
+ def symbols
52
+ type_klass::SET.symbols
53
+ end
50
54
  end
51
55
 
52
- def self.symbols
53
- type_klass::SET.symbols
54
- end
56
+ delegate_to Type
55
57
  end
56
58
  end
57
59
  end
@@ -319,7 +319,16 @@ module ActionView
319
319
  # Only locals with valid variable names get set directly. Others will
320
320
  # still be available in local_assigns.
321
321
  locals = @locals - Module::RUBY_RESERVED_KEYWORDS
322
- locals = locals.grep(/\A@?(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/)
322
+ deprecated_locals = locals.grep(/\A@+/)
323
+ if deprecated_locals.any?
324
+ ActiveSupport::Deprecation.warn(<<~MSG)
325
+ Passing instance variables to `render` is deprecated.
326
+ In Rails 7.1, #{deprecated_locals.to_sentence} will be ignored.
327
+ MSG
328
+ locals = locals.grep(/\A@?(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/)
329
+ else
330
+ locals = locals.grep(/\A(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/)
331
+ end
323
332
 
324
333
  # Assign for the same variable is to suppress unused variable warning
325
334
  locals.each_with_object(+"") { |key, code| code << "#{key} = local_assigns[:#{key}]; #{key} = #{key};" }
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ class TemplateDetails # :nodoc:
5
+ class Requested
6
+ attr_reader :locale, :handlers, :formats, :variants
7
+ attr_reader :locale_idx, :handlers_idx, :formats_idx, :variants_idx
8
+
9
+ ANY_HASH = Hash.new(1).merge(nil => 0).freeze
10
+
11
+ def initialize(locale:, handlers:, formats:, variants:)
12
+ @locale = locale
13
+ @handlers = handlers
14
+ @formats = formats
15
+ @variants = variants
16
+
17
+ @locale_idx = build_idx_hash(locale)
18
+ @handlers_idx = build_idx_hash(handlers)
19
+ @formats_idx = build_idx_hash(formats)
20
+ if variants == :any
21
+ @variants_idx = ANY_HASH
22
+ else
23
+ @variants_idx = build_idx_hash(variants)
24
+ end
25
+ end
26
+
27
+ private
28
+ def build_idx_hash(arr)
29
+ [*arr, nil].each_with_index.to_h.freeze
30
+ end
31
+ end
32
+
33
+ attr_reader :locale, :handler, :format, :variant
34
+
35
+ def initialize(locale, handler, format, variant)
36
+ @locale = locale
37
+ @handler = handler
38
+ @format = format
39
+ @variant = variant
40
+ end
41
+
42
+ def matches?(requested)
43
+ requested.formats_idx[@format] &&
44
+ requested.locale_idx[@locale] &&
45
+ requested.variants_idx[@variant] &&
46
+ requested.handlers_idx[@handler]
47
+ end
48
+
49
+ def sort_key_for(requested)
50
+ [
51
+ requested.formats_idx[@format],
52
+ requested.locale_idx[@locale],
53
+ requested.variants_idx[@variant],
54
+ requested.handlers_idx[@handler]
55
+ ]
56
+ end
57
+
58
+ def handler_class
59
+ Template.handler_for_extension(handler)
60
+ end
61
+
62
+ def format_or_default
63
+ format || handler_class.try(:default_format)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ # Represents a template path within ActionView's lookup and rendering system,
5
+ # like "users/show"
6
+ #
7
+ # TemplatePath makes it convenient to convert between separate name, prefix,
8
+ # partial arguments and the virtual path.
9
+ class TemplatePath
10
+ attr_reader :name, :prefix, :partial, :virtual
11
+ alias_method :partial?, :partial
12
+ alias_method :virtual_path, :virtual
13
+
14
+ # Convert name, prefix, and partial into a virtual path string
15
+ def self.virtual(name, prefix, partial)
16
+ if prefix.empty?
17
+ "#{partial ? "_" : ""}#{name}"
18
+ elsif partial
19
+ "#{prefix}/_#{name}"
20
+ else
21
+ "#{prefix}/#{name}"
22
+ end
23
+ end
24
+
25
+ # Build a TemplatePath form a virtual path
26
+ def self.parse(virtual)
27
+ if nameidx = virtual.rindex("/")
28
+ prefix = virtual[0, nameidx]
29
+ name = virtual.from(nameidx + 1)
30
+ prefix = prefix[1..] if prefix.start_with?("/")
31
+ else
32
+ prefix = ""
33
+ name = virtual
34
+ end
35
+ partial = name.start_with?("_")
36
+ name = name[1..] if partial
37
+ new name, prefix, partial, virtual
38
+ end
39
+
40
+ # Convert name, prefix, and partial into a TemplatePath
41
+ def self.build(name, prefix, partial)
42
+ new name, prefix, partial, virtual(name, prefix, partial)
43
+ end
44
+
45
+ def initialize(name, prefix, partial, virtual)
46
+ @name = name
47
+ @prefix = prefix
48
+ @partial = partial
49
+ @virtual = virtual
50
+ end
51
+
52
+ alias :to_str :virtual
53
+ alias :to_s :virtual
54
+
55
+ def hash # :nodoc:
56
+ @virtual.hash
57
+ end
58
+
59
+ def eql?(other) # :nodoc:
60
+ @virtual == other.virtual
61
+ end
62
+ alias :== :eql? # :nodoc:
63
+ end
64
+ end