actionview 6.0.0

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 (113) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +271 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +40 -0
  5. data/lib/action_view.rb +98 -0
  6. data/lib/action_view/base.rb +312 -0
  7. data/lib/action_view/buffers.rb +67 -0
  8. data/lib/action_view/cache_expiry.rb +54 -0
  9. data/lib/action_view/context.rb +32 -0
  10. data/lib/action_view/dependency_tracker.rb +175 -0
  11. data/lib/action_view/digestor.rb +126 -0
  12. data/lib/action_view/flows.rb +76 -0
  13. data/lib/action_view/gem_version.rb +17 -0
  14. data/lib/action_view/helpers.rb +66 -0
  15. data/lib/action_view/helpers/active_model_helper.rb +55 -0
  16. data/lib/action_view/helpers/asset_tag_helper.rb +488 -0
  17. data/lib/action_view/helpers/asset_url_helper.rb +470 -0
  18. data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
  19. data/lib/action_view/helpers/cache_helper.rb +271 -0
  20. data/lib/action_view/helpers/capture_helper.rb +216 -0
  21. data/lib/action_view/helpers/controller_helper.rb +36 -0
  22. data/lib/action_view/helpers/csp_helper.rb +26 -0
  23. data/lib/action_view/helpers/csrf_helper.rb +35 -0
  24. data/lib/action_view/helpers/date_helper.rb +1200 -0
  25. data/lib/action_view/helpers/debug_helper.rb +36 -0
  26. data/lib/action_view/helpers/form_helper.rb +2569 -0
  27. data/lib/action_view/helpers/form_options_helper.rb +896 -0
  28. data/lib/action_view/helpers/form_tag_helper.rb +920 -0
  29. data/lib/action_view/helpers/javascript_helper.rb +95 -0
  30. data/lib/action_view/helpers/number_helper.rb +456 -0
  31. data/lib/action_view/helpers/output_safety_helper.rb +70 -0
  32. data/lib/action_view/helpers/rendering_helper.rb +101 -0
  33. data/lib/action_view/helpers/sanitize_helper.rb +171 -0
  34. data/lib/action_view/helpers/tag_helper.rb +314 -0
  35. data/lib/action_view/helpers/tags.rb +44 -0
  36. data/lib/action_view/helpers/tags/base.rb +196 -0
  37. data/lib/action_view/helpers/tags/check_box.rb +66 -0
  38. data/lib/action_view/helpers/tags/checkable.rb +18 -0
  39. data/lib/action_view/helpers/tags/collection_check_boxes.rb +36 -0
  40. data/lib/action_view/helpers/tags/collection_helpers.rb +119 -0
  41. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
  42. data/lib/action_view/helpers/tags/collection_select.rb +30 -0
  43. data/lib/action_view/helpers/tags/color_field.rb +27 -0
  44. data/lib/action_view/helpers/tags/date_field.rb +15 -0
  45. data/lib/action_view/helpers/tags/date_select.rb +74 -0
  46. data/lib/action_view/helpers/tags/datetime_field.rb +32 -0
  47. data/lib/action_view/helpers/tags/datetime_local_field.rb +21 -0
  48. data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
  49. data/lib/action_view/helpers/tags/email_field.rb +10 -0
  50. data/lib/action_view/helpers/tags/file_field.rb +10 -0
  51. data/lib/action_view/helpers/tags/grouped_collection_select.rb +31 -0
  52. data/lib/action_view/helpers/tags/hidden_field.rb +10 -0
  53. data/lib/action_view/helpers/tags/label.rb +81 -0
  54. data/lib/action_view/helpers/tags/month_field.rb +15 -0
  55. data/lib/action_view/helpers/tags/number_field.rb +20 -0
  56. data/lib/action_view/helpers/tags/password_field.rb +14 -0
  57. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  58. data/lib/action_view/helpers/tags/radio_button.rb +33 -0
  59. data/lib/action_view/helpers/tags/range_field.rb +10 -0
  60. data/lib/action_view/helpers/tags/search_field.rb +27 -0
  61. data/lib/action_view/helpers/tags/select.rb +43 -0
  62. data/lib/action_view/helpers/tags/tel_field.rb +10 -0
  63. data/lib/action_view/helpers/tags/text_area.rb +24 -0
  64. data/lib/action_view/helpers/tags/text_field.rb +34 -0
  65. data/lib/action_view/helpers/tags/time_field.rb +15 -0
  66. data/lib/action_view/helpers/tags/time_select.rb +10 -0
  67. data/lib/action_view/helpers/tags/time_zone_select.rb +22 -0
  68. data/lib/action_view/helpers/tags/translator.rb +39 -0
  69. data/lib/action_view/helpers/tags/url_field.rb +10 -0
  70. data/lib/action_view/helpers/tags/week_field.rb +15 -0
  71. data/lib/action_view/helpers/text_helper.rb +486 -0
  72. data/lib/action_view/helpers/translation_helper.rb +145 -0
  73. data/lib/action_view/helpers/url_helper.rb +676 -0
  74. data/lib/action_view/layouts.rb +433 -0
  75. data/lib/action_view/locale/en.yml +56 -0
  76. data/lib/action_view/log_subscriber.rb +96 -0
  77. data/lib/action_view/lookup_context.rb +316 -0
  78. data/lib/action_view/model_naming.rb +14 -0
  79. data/lib/action_view/path_set.rb +95 -0
  80. data/lib/action_view/railtie.rb +105 -0
  81. data/lib/action_view/record_identifier.rb +112 -0
  82. data/lib/action_view/renderer/abstract_renderer.rb +108 -0
  83. data/lib/action_view/renderer/partial_renderer.rb +563 -0
  84. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +103 -0
  85. data/lib/action_view/renderer/renderer.rb +68 -0
  86. data/lib/action_view/renderer/streaming_template_renderer.rb +105 -0
  87. data/lib/action_view/renderer/template_renderer.rb +108 -0
  88. data/lib/action_view/rendering.rb +171 -0
  89. data/lib/action_view/routing_url_for.rb +146 -0
  90. data/lib/action_view/tasks/cache_digests.rake +25 -0
  91. data/lib/action_view/template.rb +393 -0
  92. data/lib/action_view/template/error.rb +161 -0
  93. data/lib/action_view/template/handlers.rb +92 -0
  94. data/lib/action_view/template/handlers/builder.rb +25 -0
  95. data/lib/action_view/template/handlers/erb.rb +84 -0
  96. data/lib/action_view/template/handlers/erb/erubi.rb +87 -0
  97. data/lib/action_view/template/handlers/html.rb +11 -0
  98. data/lib/action_view/template/handlers/raw.rb +11 -0
  99. data/lib/action_view/template/html.rb +43 -0
  100. data/lib/action_view/template/inline.rb +22 -0
  101. data/lib/action_view/template/raw_file.rb +28 -0
  102. data/lib/action_view/template/resolver.rb +394 -0
  103. data/lib/action_view/template/sources.rb +13 -0
  104. data/lib/action_view/template/sources/file.rb +17 -0
  105. data/lib/action_view/template/text.rb +35 -0
  106. data/lib/action_view/template/types.rb +57 -0
  107. data/lib/action_view/test_case.rb +300 -0
  108. data/lib/action_view/testing/resolvers.rb +67 -0
  109. data/lib/action_view/unbound_template.rb +32 -0
  110. data/lib/action_view/version.rb +10 -0
  111. data/lib/action_view/view_paths.rb +129 -0
  112. data/lib/assets/compiled/rails-ujs.js +746 -0
  113. metadata +260 -0
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Template::Handlers
5
+ class Html < Raw
6
+ def call(template, source)
7
+ "ActionView::OutputBuffer.new #{super}"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Template::Handlers
5
+ class Raw
6
+ def call(template, source)
7
+ "#{source.inspect}.html_safe;"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/deprecation"
4
+
5
+ module ActionView #:nodoc:
6
+ # = Action View HTML Template
7
+ class Template #:nodoc:
8
+ class HTML #:nodoc:
9
+ attr_reader :type
10
+
11
+ def initialize(string, type = nil)
12
+ unless type
13
+ ActiveSupport::Deprecation.warn "ActionView::Template::HTML#initialize requires a type parameter"
14
+ type = :html
15
+ end
16
+
17
+ @string = string.to_s
18
+ @type = type
19
+ end
20
+
21
+ def identifier
22
+ "html template"
23
+ end
24
+
25
+ alias_method :inspect, :identifier
26
+
27
+ def to_str
28
+ ERB::Util.h(@string)
29
+ end
30
+
31
+ def render(*args)
32
+ to_str
33
+ end
34
+
35
+ def format
36
+ @type
37
+ end
38
+
39
+ def formats; Array(format); end
40
+ deprecate :formats
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView #:nodoc:
4
+ class Template #:nodoc:
5
+ class Inline < Template #:nodoc:
6
+ # This finalizer is needed (and exactly with a proc inside another proc)
7
+ # otherwise templates leak in development.
8
+ Finalizer = proc do |method_name, mod| # :nodoc:
9
+ proc do
10
+ mod.module_eval do
11
+ remove_possible_method method_name
12
+ end
13
+ end
14
+ end
15
+
16
+ def compile(mod)
17
+ super
18
+ ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView #:nodoc:
4
+ # = Action View RawFile Template
5
+ class Template #:nodoc:
6
+ class RawFile #:nodoc:
7
+ attr_accessor :type, :format
8
+
9
+ def initialize(filename)
10
+ @filename = filename.to_s
11
+ extname = ::File.extname(filename).delete(".")
12
+ @type = Template::Types[extname] || Template::Types[:text]
13
+ @format = @type.symbol
14
+ end
15
+
16
+ def identifier
17
+ @filename
18
+ end
19
+
20
+ def render(*args)
21
+ ::File.read(@filename)
22
+ end
23
+
24
+ def formats; Array(format); end
25
+ deprecate :formats
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,394 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+ require "active_support/core_ext/class"
5
+ require "active_support/core_ext/module/attribute_accessors"
6
+ require "action_view/template"
7
+ require "thread"
8
+ require "concurrent/map"
9
+
10
+ module ActionView
11
+ # = Action View Resolver
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
37
+
38
+ # Threadsafe template cache
39
+ class Cache #:nodoc:
40
+ class SmallCache < Concurrent::Map
41
+ def initialize(options = {})
42
+ super(options.merge(initial_capacity: 2))
43
+ end
44
+ end
45
+
46
+ # preallocate all the default blocks for performance/memory consumption reasons
47
+ PARTIAL_BLOCK = lambda { |cache, partial| cache[partial] = SmallCache.new }
48
+ PREFIX_BLOCK = lambda { |cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK) }
49
+ NAME_BLOCK = lambda { |cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK) }
50
+ KEY_BLOCK = lambda { |cache, key| cache[key] = SmallCache.new(&NAME_BLOCK) }
51
+
52
+ # usually a majority of template look ups return nothing, use this canonical preallocated array to save memory
53
+ NO_TEMPLATES = [].freeze
54
+
55
+ def initialize
56
+ @data = SmallCache.new(&KEY_BLOCK)
57
+ @query_cache = SmallCache.new
58
+ end
59
+
60
+ def inspect
61
+ "#<#{self.class.name}:0x#{(object_id << 1).to_s(16)} keys=#{@data.size} queries=#{@query_cache.size}>"
62
+ end
63
+
64
+ # Cache the templates returned by the block
65
+ def cache(key, name, prefix, partial, locals)
66
+ @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
67
+ end
68
+
69
+ def cache_query(query) # :nodoc:
70
+ @query_cache[query] ||= canonical_no_templates(yield)
71
+ end
72
+
73
+ def clear
74
+ @data.clear
75
+ @query_cache.clear
76
+ end
77
+
78
+ # Get the cache size. Do not call this
79
+ # method. This method is not guaranteed to be here ever.
80
+ def size # :nodoc:
81
+ size = 0
82
+ @data.each_value do |v1|
83
+ v1.each_value do |v2|
84
+ v2.each_value do |v3|
85
+ v3.each_value do |v4|
86
+ size += v4.size
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ size + @query_cache.size
93
+ end
94
+
95
+ private
96
+
97
+ def canonical_no_templates(templates)
98
+ templates.empty? ? NO_TEMPLATES : templates
99
+ end
100
+ end
101
+
102
+ cattr_accessor :caching, default: true
103
+
104
+ class << self
105
+ alias :caching? :caching
106
+ end
107
+
108
+ def initialize
109
+ @cache = Cache.new
110
+ end
111
+
112
+ def clear_cache
113
+ @cache.clear
114
+ end
115
+
116
+ # Normalizes the arguments and passes it on to find_templates.
117
+ def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = [])
118
+ locals = locals.map(&:to_s).sort!.freeze
119
+
120
+ cached(key, [name, prefix, partial], details, locals) do
121
+ _find_all(name, prefix, partial, details, key, locals)
122
+ end
123
+ end
124
+
125
+ alias :find_all_anywhere :find_all
126
+ deprecate :find_all_anywhere
127
+
128
+ def find_all_with_query(query) # :nodoc:
129
+ @cache.cache_query(query) { find_template_paths(File.join(@path, query)) }
130
+ end
131
+
132
+ private
133
+
134
+ def _find_all(name, prefix, partial, details, key, locals)
135
+ find_templates(name, prefix, partial, details, locals)
136
+ end
137
+
138
+ delegate :caching?, to: :class
139
+
140
+ # This is what child classes implement. No defaults are needed
141
+ # because Resolver guarantees that the arguments are present and
142
+ # normalized.
143
+ def find_templates(name, prefix, partial, details, locals = [])
144
+ raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, locals = []) method"
145
+ end
146
+
147
+ # Handles templates caching. If a key is given and caching is on
148
+ # always check the cache before hitting the resolver. Otherwise,
149
+ # it always hits the resolver but if the key is present, check if the
150
+ # resolver is fresher before returning it.
151
+ def cached(key, path_info, details, locals)
152
+ name, prefix, partial = path_info
153
+
154
+ if key
155
+ @cache.cache(key, name, prefix, partial, locals) do
156
+ yield
157
+ end
158
+ else
159
+ yield
160
+ end
161
+ end
162
+ end
163
+
164
+ # An abstract class that implements a Resolver with path semantics.
165
+ class PathResolver < Resolver #:nodoc:
166
+ EXTENSIONS = { locale: ".", formats: ".", variants: "+", handlers: "." }
167
+ DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
168
+
169
+ def initialize(pattern = nil)
170
+ if pattern
171
+ ActiveSupport::Deprecation.warn "Specifying a custom path for #{self.class} is deprecated. Implement a custom Resolver subclass instead."
172
+ @pattern = pattern
173
+ else
174
+ @pattern = DEFAULT_PATTERN
175
+ end
176
+ @unbound_templates = Concurrent::Map.new
177
+ super()
178
+ end
179
+
180
+ def clear_cache
181
+ @unbound_templates.clear
182
+ super()
183
+ end
184
+
185
+ private
186
+
187
+ def _find_all(name, prefix, partial, details, key, locals)
188
+ path = Path.build(name, prefix, partial)
189
+ query(path, details, details[:formats], locals, cache: !!key)
190
+ end
191
+
192
+ def query(path, details, formats, locals, cache:)
193
+ template_paths = find_template_paths_from_details(path, details)
194
+ template_paths = reject_files_external_to_app(template_paths)
195
+
196
+ template_paths.map do |template|
197
+ unbound_template =
198
+ if cache
199
+ @unbound_templates.compute_if_absent([template, path.virtual]) do
200
+ build_unbound_template(template, path.virtual)
201
+ end
202
+ else
203
+ build_unbound_template(template, path.virtual)
204
+ end
205
+
206
+ unbound_template.bind_locals(locals)
207
+ end
208
+ end
209
+
210
+ def build_unbound_template(template, virtual_path)
211
+ handler, format, variant = extract_handler_and_format_and_variant(template)
212
+ source = Template::Sources::File.new(template)
213
+
214
+ UnboundTemplate.new(
215
+ source,
216
+ template,
217
+ handler,
218
+ virtual_path: virtual_path,
219
+ format: format,
220
+ variant: variant,
221
+ )
222
+ end
223
+
224
+ def reject_files_external_to_app(files)
225
+ files.reject { |filename| !inside_path?(@path, filename) }
226
+ end
227
+
228
+ def find_template_paths_from_details(path, details)
229
+ query = build_query(path, details)
230
+ find_template_paths(query)
231
+ end
232
+
233
+ def find_template_paths(query)
234
+ Dir[query].uniq.reject do |filename|
235
+ File.directory?(filename) ||
236
+ # deals with case-insensitive file systems.
237
+ !File.fnmatch(query, filename, File::FNM_EXTGLOB)
238
+ end
239
+ end
240
+
241
+ def inside_path?(path, filename)
242
+ filename = File.expand_path(filename)
243
+ path = File.join(path, "")
244
+ filename.start_with?(path)
245
+ end
246
+
247
+ # Helper for building query glob string based on resolver's pattern.
248
+ def build_query(path, details)
249
+ query = @pattern.dup
250
+
251
+ prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
252
+ query.gsub!(/:prefix(\/)?/, prefix)
253
+
254
+ partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
255
+ query.gsub!(/:action/, partial)
256
+
257
+ details.each do |ext, candidates|
258
+ if ext == :variants && candidates == :any
259
+ query.gsub!(/:#{ext}/, "*")
260
+ else
261
+ query.gsub!(/:#{ext}/, "{#{candidates.compact.uniq.join(',')}}")
262
+ end
263
+ end
264
+
265
+ File.expand_path(query, @path)
266
+ end
267
+
268
+ def escape_entry(entry)
269
+ entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
270
+ end
271
+
272
+ # Extract handler, formats and variant from path. If a format cannot be found neither
273
+ # from the path, or the handler, we should return the array of formats given
274
+ # to the resolver.
275
+ def extract_handler_and_format_and_variant(path)
276
+ pieces = File.basename(path).split(".")
277
+ pieces.shift
278
+
279
+ extension = pieces.pop
280
+
281
+ handler = Template.handler_for_extension(extension)
282
+ format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
283
+ format = if format
284
+ Template::Types[format]&.ref
285
+ else
286
+ if handler.respond_to?(:default_format) # default_format can return nil
287
+ handler.default_format
288
+ else
289
+ nil
290
+ end
291
+ end
292
+
293
+ # Template::Types[format] and handler.default_format can return nil
294
+ [handler, format, variant]
295
+ end
296
+ end
297
+
298
+ # A resolver that loads files from the filesystem.
299
+ class FileSystemResolver < PathResolver
300
+ attr_reader :path
301
+
302
+ def initialize(path, pattern = nil)
303
+ raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
304
+ super(pattern)
305
+ @path = File.expand_path(path)
306
+ end
307
+
308
+ def to_s
309
+ @path.to_s
310
+ end
311
+ alias :to_path :to_s
312
+
313
+ def eql?(resolver)
314
+ self.class.equal?(resolver.class) && to_path == resolver.to_path
315
+ end
316
+ alias :== :eql?
317
+ end
318
+
319
+ # An Optimized resolver for Rails' most common case.
320
+ class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
321
+ def initialize(path)
322
+ super(path)
323
+ end
324
+
325
+ private
326
+
327
+ def find_template_paths_from_details(path, details)
328
+ # Instead of checking for every possible path, as our other globs would
329
+ # do, scan the directory for files with the right prefix.
330
+ query = "#{escape_entry(File.join(@path, path))}*"
331
+
332
+ regex = build_regex(path, details)
333
+
334
+ Dir[query].uniq.reject do |filename|
335
+ # This regex match does double duty of finding only files which match
336
+ # details (instead of just matching the prefix) and also filtering for
337
+ # case-insensitive file systems.
338
+ !regex.match?(filename) ||
339
+ File.directory?(filename)
340
+ end.sort_by do |filename|
341
+ # Because we scanned the directory, instead of checking for files
342
+ # one-by-one, they will be returned in an arbitrary order.
343
+ # We can use the matches found by the regex and sort by their index in
344
+ # details.
345
+ match = filename.match(regex)
346
+ EXTENSIONS.keys.reverse.map do |ext|
347
+ if ext == :variants && details[ext] == :any
348
+ match[ext].nil? ? 0 : 1
349
+ elsif match[ext].nil?
350
+ # No match should be last
351
+ details[ext].length
352
+ else
353
+ found = match[ext].to_sym
354
+ details[ext].index(found)
355
+ end
356
+ end
357
+ end
358
+ end
359
+
360
+ def build_regex(path, details)
361
+ query = escape_entry(File.join(@path, path))
362
+ exts = EXTENSIONS.map do |ext, prefix|
363
+ match =
364
+ if ext == :variants && details[ext] == :any
365
+ ".*?"
366
+ else
367
+ details[ext].compact.uniq.map { |e| Regexp.escape(e) }.join("|")
368
+ end
369
+ prefix = Regexp.escape(prefix)
370
+ "(#{prefix}(?<#{ext}>#{match}))?"
371
+ end.join
372
+
373
+ %r{\A#{query}#{exts}\z}
374
+ end
375
+ end
376
+
377
+ # The same as FileSystemResolver but does not allow templates to store
378
+ # a virtual path since it is invalid for such resolvers.
379
+ class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
380
+ private_class_method :new
381
+
382
+ def self.instances
383
+ [new(""), new("/")]
384
+ end
385
+
386
+ def build_unbound_template(template, _)
387
+ super(template, nil)
388
+ end
389
+
390
+ def reject_files_external_to_app(files)
391
+ files
392
+ end
393
+ end
394
+ end