omg-actionview 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +25 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +40 -0
  5. data/app/assets/javascripts/rails-ujs.esm.js +686 -0
  6. data/app/assets/javascripts/rails-ujs.js +630 -0
  7. data/lib/action_view/base.rb +316 -0
  8. data/lib/action_view/buffers.rb +165 -0
  9. data/lib/action_view/cache_expiry.rb +69 -0
  10. data/lib/action_view/context.rb +32 -0
  11. data/lib/action_view/dependency_tracker/erb_tracker.rb +159 -0
  12. data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
  13. data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
  14. data/lib/action_view/dependency_tracker.rb +41 -0
  15. data/lib/action_view/deprecator.rb +7 -0
  16. data/lib/action_view/digestor.rb +130 -0
  17. data/lib/action_view/flows.rb +75 -0
  18. data/lib/action_view/gem_version.rb +17 -0
  19. data/lib/action_view/helpers/active_model_helper.rb +54 -0
  20. data/lib/action_view/helpers/asset_tag_helper.rb +680 -0
  21. data/lib/action_view/helpers/asset_url_helper.rb +473 -0
  22. data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
  23. data/lib/action_view/helpers/cache_helper.rb +315 -0
  24. data/lib/action_view/helpers/capture_helper.rb +236 -0
  25. data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
  26. data/lib/action_view/helpers/controller_helper.rb +42 -0
  27. data/lib/action_view/helpers/csp_helper.rb +26 -0
  28. data/lib/action_view/helpers/csrf_helper.rb +35 -0
  29. data/lib/action_view/helpers/date_helper.rb +1266 -0
  30. data/lib/action_view/helpers/debug_helper.rb +38 -0
  31. data/lib/action_view/helpers/form_helper.rb +2765 -0
  32. data/lib/action_view/helpers/form_options_helper.rb +927 -0
  33. data/lib/action_view/helpers/form_tag_helper.rb +1088 -0
  34. data/lib/action_view/helpers/javascript_helper.rb +96 -0
  35. data/lib/action_view/helpers/number_helper.rb +165 -0
  36. data/lib/action_view/helpers/output_safety_helper.rb +70 -0
  37. data/lib/action_view/helpers/rendering_helper.rb +218 -0
  38. data/lib/action_view/helpers/sanitize_helper.rb +201 -0
  39. data/lib/action_view/helpers/tag_helper.rb +621 -0
  40. data/lib/action_view/helpers/tags/base.rb +138 -0
  41. data/lib/action_view/helpers/tags/check_box.rb +65 -0
  42. data/lib/action_view/helpers/tags/checkable.rb +18 -0
  43. data/lib/action_view/helpers/tags/collection_check_boxes.rb +37 -0
  44. data/lib/action_view/helpers/tags/collection_helpers.rb +118 -0
  45. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
  46. data/lib/action_view/helpers/tags/collection_select.rb +33 -0
  47. data/lib/action_view/helpers/tags/color_field.rb +26 -0
  48. data/lib/action_view/helpers/tags/date_field.rb +14 -0
  49. data/lib/action_view/helpers/tags/date_select.rb +75 -0
  50. data/lib/action_view/helpers/tags/datetime_field.rb +39 -0
  51. data/lib/action_view/helpers/tags/datetime_local_field.rb +29 -0
  52. data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
  53. data/lib/action_view/helpers/tags/email_field.rb +10 -0
  54. data/lib/action_view/helpers/tags/file_field.rb +26 -0
  55. data/lib/action_view/helpers/tags/grouped_collection_select.rb +34 -0
  56. data/lib/action_view/helpers/tags/hidden_field.rb +14 -0
  57. data/lib/action_view/helpers/tags/label.rb +84 -0
  58. data/lib/action_view/helpers/tags/month_field.rb +14 -0
  59. data/lib/action_view/helpers/tags/number_field.rb +20 -0
  60. data/lib/action_view/helpers/tags/password_field.rb +14 -0
  61. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  62. data/lib/action_view/helpers/tags/radio_button.rb +32 -0
  63. data/lib/action_view/helpers/tags/range_field.rb +10 -0
  64. data/lib/action_view/helpers/tags/search_field.rb +27 -0
  65. data/lib/action_view/helpers/tags/select.rb +45 -0
  66. data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
  67. data/lib/action_view/helpers/tags/tel_field.rb +10 -0
  68. data/lib/action_view/helpers/tags/text_area.rb +24 -0
  69. data/lib/action_view/helpers/tags/text_field.rb +33 -0
  70. data/lib/action_view/helpers/tags/time_field.rb +23 -0
  71. data/lib/action_view/helpers/tags/time_select.rb +10 -0
  72. data/lib/action_view/helpers/tags/time_zone_select.rb +25 -0
  73. data/lib/action_view/helpers/tags/translator.rb +39 -0
  74. data/lib/action_view/helpers/tags/url_field.rb +10 -0
  75. data/lib/action_view/helpers/tags/week_field.rb +14 -0
  76. data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
  77. data/lib/action_view/helpers/tags.rb +47 -0
  78. data/lib/action_view/helpers/text_helper.rb +568 -0
  79. data/lib/action_view/helpers/translation_helper.rb +161 -0
  80. data/lib/action_view/helpers/url_helper.rb +812 -0
  81. data/lib/action_view/helpers.rb +68 -0
  82. data/lib/action_view/layouts.rb +434 -0
  83. data/lib/action_view/locale/en.yml +56 -0
  84. data/lib/action_view/log_subscriber.rb +132 -0
  85. data/lib/action_view/lookup_context.rb +299 -0
  86. data/lib/action_view/model_naming.rb +14 -0
  87. data/lib/action_view/path_registry.rb +57 -0
  88. data/lib/action_view/path_set.rb +84 -0
  89. data/lib/action_view/railtie.rb +132 -0
  90. data/lib/action_view/record_identifier.rb +118 -0
  91. data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
  92. data/lib/action_view/render_parser/ripper_render_parser.rb +350 -0
  93. data/lib/action_view/render_parser.rb +40 -0
  94. data/lib/action_view/renderer/abstract_renderer.rb +186 -0
  95. data/lib/action_view/renderer/collection_renderer.rb +204 -0
  96. data/lib/action_view/renderer/object_renderer.rb +34 -0
  97. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +120 -0
  98. data/lib/action_view/renderer/partial_renderer.rb +267 -0
  99. data/lib/action_view/renderer/renderer.rb +107 -0
  100. data/lib/action_view/renderer/streaming_template_renderer.rb +107 -0
  101. data/lib/action_view/renderer/template_renderer.rb +115 -0
  102. data/lib/action_view/rendering.rb +190 -0
  103. data/lib/action_view/routing_url_for.rb +149 -0
  104. data/lib/action_view/tasks/cache_digests.rake +25 -0
  105. data/lib/action_view/template/error.rb +264 -0
  106. data/lib/action_view/template/handlers/builder.rb +25 -0
  107. data/lib/action_view/template/handlers/erb/erubi.rb +85 -0
  108. data/lib/action_view/template/handlers/erb.rb +157 -0
  109. data/lib/action_view/template/handlers/html.rb +11 -0
  110. data/lib/action_view/template/handlers/raw.rb +11 -0
  111. data/lib/action_view/template/handlers.rb +66 -0
  112. data/lib/action_view/template/html.rb +33 -0
  113. data/lib/action_view/template/inline.rb +22 -0
  114. data/lib/action_view/template/raw_file.rb +25 -0
  115. data/lib/action_view/template/renderable.rb +30 -0
  116. data/lib/action_view/template/resolver.rb +212 -0
  117. data/lib/action_view/template/sources/file.rb +17 -0
  118. data/lib/action_view/template/sources.rb +13 -0
  119. data/lib/action_view/template/text.rb +32 -0
  120. data/lib/action_view/template/types.rb +50 -0
  121. data/lib/action_view/template.rb +580 -0
  122. data/lib/action_view/template_details.rb +66 -0
  123. data/lib/action_view/template_path.rb +66 -0
  124. data/lib/action_view/test_case.rb +449 -0
  125. data/lib/action_view/testing/resolvers.rb +44 -0
  126. data/lib/action_view/unbound_template.rb +67 -0
  127. data/lib/action_view/version.rb +10 -0
  128. data/lib/action_view/view_paths.rb +117 -0
  129. data/lib/action_view.rb +104 -0
  130. metadata +275 -0
@@ -0,0 +1,580 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+
5
+ module ActionView
6
+ # = Action View \Template
7
+ class Template
8
+ extend ActiveSupport::Autoload
9
+
10
+ STRICT_LOCALS_REGEX = /\#\s+locals:\s+\((.*)\)/
11
+
12
+ # === Encodings in ActionView::Template
13
+ #
14
+ # ActionView::Template is one of a few sources of potential
15
+ # encoding issues in \Rails. This is because the source for
16
+ # templates are usually read from disk, and Ruby (like most
17
+ # encoding-aware programming languages) assumes that the
18
+ # String retrieved through File IO is encoded in the
19
+ # <tt>default_external</tt> encoding. In \Rails, the default
20
+ # <tt>default_external</tt> encoding is UTF-8.
21
+ #
22
+ # As a result, if a user saves their template as ISO-8859-1
23
+ # (for instance, using a non-Unicode-aware text editor),
24
+ # and uses characters outside of the ASCII range, their
25
+ # users will see diamonds with question marks in them in
26
+ # the browser.
27
+ #
28
+ # For the rest of this documentation, when we say "UTF-8",
29
+ # we mean "UTF-8 or whatever the default_internal encoding
30
+ # is set to". By default, it will be UTF-8.
31
+ #
32
+ # To mitigate this problem, we use a few strategies:
33
+ # 1. If the source is not valid UTF-8, we raise an exception
34
+ # when the template is compiled to alert the user
35
+ # to the problem.
36
+ # 2. The user can specify the encoding using Ruby-style
37
+ # encoding comments in any template engine. If such
38
+ # a comment is supplied, \Rails will apply that encoding
39
+ # to the resulting compiled source returned by the
40
+ # template handler.
41
+ # 3. In all cases, we transcode the resulting String to
42
+ # the UTF-8.
43
+ #
44
+ # This means that other parts of \Rails can always assume
45
+ # that templates are encoded in UTF-8, even if the original
46
+ # source of the template was not UTF-8.
47
+ #
48
+ # From a user's perspective, the easiest thing to do is
49
+ # to save your templates as UTF-8. If you do this, you
50
+ # do not need to do anything else for things to "just work".
51
+ #
52
+ # === Instructions for template handlers
53
+ #
54
+ # The easiest thing for you to do is to simply ignore
55
+ # encodings. \Rails will hand you the template source
56
+ # as the default_internal (generally UTF-8), raising
57
+ # an exception for the user before sending the template
58
+ # to you if it could not determine the original encoding.
59
+ #
60
+ # For the greatest simplicity, you can support only
61
+ # UTF-8 as the <tt>default_internal</tt>. This means
62
+ # that from the perspective of your handler, the
63
+ # entire pipeline is just UTF-8.
64
+ #
65
+ # === Advanced: Handlers with alternate metadata sources
66
+ #
67
+ # If you want to provide an alternate mechanism for
68
+ # specifying encodings (like ERB does via <%# encoding: ... %>),
69
+ # you may indicate that you will handle encodings yourself
70
+ # by implementing <tt>handles_encoding?</tt> on your handler.
71
+ #
72
+ # If you do, \Rails will not try to encode the String
73
+ # into the default_internal, passing you the unaltered
74
+ # bytes tagged with the assumed encoding (from
75
+ # default_external).
76
+ #
77
+ # In this case, make sure you return a String from
78
+ # your handler encoded in the default_internal. Since
79
+ # you are handling out-of-band metadata, you are
80
+ # also responsible for alerting the user to any
81
+ # problems with converting the user's data to
82
+ # the <tt>default_internal</tt>.
83
+ #
84
+ # To do so, simply raise +WrongEncodingError+ as follows:
85
+ #
86
+ # raise WrongEncodingError.new(
87
+ # problematic_string,
88
+ # expected_encoding
89
+ # )
90
+
91
+ ##
92
+ # :method: local_assigns
93
+ #
94
+ # Returns a hash with the defined local variables.
95
+ #
96
+ # Given this sub template rendering:
97
+ #
98
+ # <%= render "application/header", { headline: "Welcome", person: person } %>
99
+ #
100
+ # You can use +local_assigns+ in the sub templates to access the local variables:
101
+ #
102
+ # local_assigns[:headline] # => "Welcome"
103
+ #
104
+ # Each key in +local_assigns+ is available as a partial-local variable:
105
+ #
106
+ # local_assigns[:headline] # => "Welcome"
107
+ # headline # => "Welcome"
108
+ #
109
+ # Since +local_assigns+ is a +Hash+, it's compatible with Ruby 3.1's pattern
110
+ # matching assignment operator:
111
+ #
112
+ # local_assigns => { headline:, **options }
113
+ # headline # => "Welcome"
114
+ # options # => {}
115
+ #
116
+ # Pattern matching assignment also supports variable renaming:
117
+ #
118
+ # local_assigns => { headline: title }
119
+ # title # => "Welcome"
120
+ #
121
+ # If a template refers to a variable that isn't passed into the view as part
122
+ # of the <tt>locals: { ... }</tt> Hash, the template will raise an
123
+ # +ActionView::Template::Error+:
124
+ #
125
+ # <%# => raises ActionView::Template::Error %>
126
+ # <% alerts.each do |alert| %>
127
+ # <p><%= alert %></p>
128
+ # <% end %>
129
+ #
130
+ # Since +local_assigns+ returns a +Hash+ instance, you can conditionally
131
+ # read a variable, then fall back to a default value when
132
+ # the key isn't part of the <tt>locals: { ... }</tt> options:
133
+ #
134
+ # <% local_assigns.fetch(:alerts, []).each do |alert| %>
135
+ # <p><%= alert %></p>
136
+ # <% end %>
137
+ #
138
+ # Combining Ruby 3.1's pattern matching assignment with calls to
139
+ # +Hash#with_defaults+ enables compact partial-local variable
140
+ # assignments:
141
+ #
142
+ # <% local_assigns.with_defaults(alerts: []) => { headline:, alerts: } %>
143
+ #
144
+ # <h1><%= headline %></h1>
145
+ #
146
+ # <% alerts.each do |alert| %>
147
+ # <p><%= alert %></p>
148
+ # <% end %>
149
+ #
150
+ # By default, templates will accept any <tt>locals</tt> as keyword arguments
151
+ # and make them available to <tt>local_assigns</tt>. To restrict what
152
+ # <tt>local_assigns</tt> a template will accept, add a <tt>locals:</tt> magic comment:
153
+ #
154
+ # <%# locals: (headline:, alerts: []) %>
155
+ #
156
+ # <h1><%= headline %></h1>
157
+ #
158
+ # <% alerts.each do |alert| %>
159
+ # <p><%= alert %></p>
160
+ # <% end %>
161
+ #
162
+ # Read more about strict locals in {Action View Overview}[https://guides.rubyonrails.org/action_view_overview.html#strict-locals]
163
+ # in the guides.
164
+
165
+ eager_autoload do
166
+ autoload :Error
167
+ autoload :RawFile
168
+ autoload :Renderable
169
+ autoload :Handlers
170
+ autoload :HTML
171
+ autoload :Inline
172
+ autoload :Types
173
+ autoload :Sources
174
+ autoload :Text
175
+ autoload :Types
176
+ end
177
+
178
+ extend Template::Handlers
179
+
180
+ singleton_class.attr_accessor :frozen_string_literal
181
+ @frozen_string_literal = false
182
+
183
+ class << self # :nodoc:
184
+ def mime_types_implementation=(implementation)
185
+ # This method isn't thread-safe, but it's not supposed
186
+ # to be called after initialization
187
+ if self::Types != implementation
188
+ remove_const(:Types)
189
+ const_set(:Types, implementation)
190
+ end
191
+ end
192
+ end
193
+
194
+ attr_reader :identifier, :handler
195
+ attr_reader :variable, :format, :variant, :virtual_path
196
+
197
+ NONE = Object.new
198
+
199
+ def initialize(source, identifier, handler, locals:, format: nil, variant: nil, virtual_path: nil)
200
+ @source = source.dup
201
+ @identifier = identifier
202
+ @handler = handler
203
+ @compiled = false
204
+ @locals = locals
205
+ @virtual_path = virtual_path
206
+
207
+ @variable = if @virtual_path
208
+ base = @virtual_path.end_with?("/") ? "" : ::File.basename(@virtual_path)
209
+ base =~ /\A_?(.*?)(?:\.\w+)*\z/
210
+ $1.to_sym
211
+ end
212
+
213
+ @format = format
214
+ @variant = variant
215
+ @compile_mutex = Mutex.new
216
+ @strict_locals = NONE
217
+ @strict_local_keys = nil
218
+ @type = nil
219
+ end
220
+
221
+ # The locals this template has been or will be compiled for, or nil if this
222
+ # is a strict locals template.
223
+ def locals
224
+ if strict_locals?
225
+ nil
226
+ else
227
+ @locals
228
+ end
229
+ end
230
+
231
+ def spot(location) # :nodoc:
232
+ ast = RubyVM::AbstractSyntaxTree.parse(compiled_source, keep_script_lines: true)
233
+ node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(location)
234
+ node = find_node_by_id(ast, node_id)
235
+
236
+ ErrorHighlight.spot(node)
237
+ end
238
+
239
+ # Translate an error location returned by ErrorHighlight to the correct
240
+ # source location inside the template.
241
+ def translate_location(backtrace_location, spot)
242
+ if handler.respond_to?(:translate_location)
243
+ handler.translate_location(spot, backtrace_location, encode!) || spot
244
+ else
245
+ spot
246
+ end
247
+ end
248
+
249
+ # Returns whether the underlying handler supports streaming. If so,
250
+ # a streaming buffer *may* be passed when it starts rendering.
251
+ def supports_streaming?
252
+ handler.respond_to?(:supports_streaming?) && handler.supports_streaming?
253
+ end
254
+
255
+ # Render a template. If the template was not compiled yet, it is done
256
+ # exactly before rendering.
257
+ #
258
+ # This method is instrumented as "!render_template.action_view". Notice that
259
+ # we use a bang in this instrumentation because you don't want to
260
+ # consume this in production. This is only slow if it's being listened to.
261
+ def render(view, locals, buffer = nil, implicit_locals: [], add_to_stack: true, &block)
262
+ instrument_render_template do
263
+ compile!(view)
264
+
265
+ if strict_locals? && @strict_local_keys && !implicit_locals.empty?
266
+ locals_to_ignore = implicit_locals - @strict_local_keys
267
+ locals.except!(*locals_to_ignore)
268
+ end
269
+
270
+ if buffer
271
+ view._run(method_name, self, locals, buffer, add_to_stack: add_to_stack, has_strict_locals: strict_locals?, &block)
272
+ nil
273
+ else
274
+ result = view._run(method_name, self, locals, OutputBuffer.new, add_to_stack: add_to_stack, has_strict_locals: strict_locals?, &block)
275
+ result.is_a?(OutputBuffer) ? result.to_s : result
276
+ end
277
+ end
278
+ rescue => e
279
+ handle_render_error(view, e)
280
+ end
281
+
282
+ def type
283
+ @type ||= Types[format]
284
+ end
285
+
286
+ def short_identifier
287
+ @short_identifier ||= defined?(Rails.root) ? identifier.delete_prefix("#{Rails.root}/") : identifier
288
+ end
289
+
290
+ def inspect
291
+ "#<#{self.class.name} #{short_identifier} locals=#{locals.inspect}>"
292
+ end
293
+
294
+ def source
295
+ @source.to_s
296
+ end
297
+
298
+ LEADING_ENCODING_REGEXP = /\A#{ENCODING_FLAG}/
299
+ private_constant :LEADING_ENCODING_REGEXP
300
+
301
+ # This method is responsible for properly setting the encoding of the
302
+ # source. Until this point, we assume that the source is BINARY data.
303
+ # If no additional information is supplied, we assume the encoding is
304
+ # the same as <tt>Encoding.default_external</tt>.
305
+ #
306
+ # The user can also specify the encoding via a comment on the first
307
+ # line of the template (<tt># encoding: NAME-OF-ENCODING</tt>). This will work
308
+ # with any template engine, as we process out the encoding comment
309
+ # before passing the source on to the template engine, leaving a
310
+ # blank line in its stead.
311
+ def encode!
312
+ source = self.source
313
+
314
+ return source unless source.encoding == Encoding::BINARY
315
+
316
+ # Look for # encoding: *. If we find one, we'll encode the
317
+ # String in that encoding, otherwise, we'll use the
318
+ # default external encoding.
319
+ if source.sub!(LEADING_ENCODING_REGEXP, "")
320
+ encoding = magic_encoding = $1
321
+ else
322
+ encoding = Encoding.default_external
323
+ end
324
+
325
+ # Tag the source with the default external encoding
326
+ # or the encoding specified in the file
327
+ source.force_encoding(encoding)
328
+
329
+ # If the user didn't specify an encoding, and the handler
330
+ # handles encodings, we simply pass the String as is to
331
+ # the handler (with the default_external tag)
332
+ if !magic_encoding && @handler.respond_to?(:handles_encoding?) && @handler.handles_encoding?
333
+ source
334
+ # Otherwise, if the String is valid in the encoding,
335
+ # encode immediately to default_internal. This means
336
+ # that if a handler doesn't handle encodings, it will
337
+ # always get Strings in the default_internal
338
+ elsif source.valid_encoding?
339
+ source.encode!
340
+ # Otherwise, since the String is invalid in the encoding
341
+ # specified, raise an exception
342
+ else
343
+ raise WrongEncodingError.new(source, encoding)
344
+ end
345
+ end
346
+
347
+ # This method is responsible for marking a template as having strict locals
348
+ # which means the template can only accept the locals defined in a magic
349
+ # comment. For example, if your template acceps the locals +title+ and
350
+ # +comment_count+, add the following to your template file:
351
+ #
352
+ # <%# locals: (title: "Default title", comment_count: 0) %>
353
+ #
354
+ # Strict locals are useful for validating template arguments and for
355
+ # specifying defaults.
356
+ def strict_locals!
357
+ if @strict_locals == NONE
358
+ self.source.sub!(STRICT_LOCALS_REGEX, "")
359
+ @strict_locals = $1
360
+
361
+ return if @strict_locals.nil? # Magic comment not found
362
+
363
+ @strict_locals = "**nil" if @strict_locals.blank?
364
+ end
365
+
366
+ @strict_locals
367
+ end
368
+
369
+ # Returns whether a template is using strict locals.
370
+ def strict_locals?
371
+ strict_locals!
372
+ end
373
+
374
+ # Exceptions are marshalled when using the parallel test runner with DRb, so we need
375
+ # to ensure that references to the template object can be marshalled as well. This means forgoing
376
+ # the marshalling of the compiler mutex and instantiating that again on unmarshalling.
377
+ def marshal_dump # :nodoc:
378
+ [ @source, @identifier, @handler, @compiled, @locals, @virtual_path, @format, @variant ]
379
+ end
380
+
381
+ def marshal_load(array) # :nodoc:
382
+ @source, @identifier, @handler, @compiled, @locals, @virtual_path, @format, @variant = *array
383
+ @compile_mutex = Mutex.new
384
+ end
385
+
386
+ def method_name # :nodoc:
387
+ @method_name ||= begin
388
+ m = +"_#{identifier_method_name}__#{@identifier.hash}_#{__id__}"
389
+ m.tr!("-", "_")
390
+ m
391
+ end
392
+ end
393
+
394
+ private
395
+ def find_node_by_id(node, node_id)
396
+ return node if node.node_id == node_id
397
+
398
+ node.children.grep(node.class).each do |child|
399
+ found = find_node_by_id(child, node_id)
400
+ return found if found
401
+ end
402
+
403
+ false
404
+ end
405
+
406
+ # Compile a template. This method ensures a template is compiled
407
+ # just once and removes the source after it is compiled.
408
+ def compile!(view)
409
+ return if @compiled
410
+
411
+ # Templates can be used concurrently in threaded environments
412
+ # so compilation and any instance variable modification must
413
+ # be synchronized
414
+ @compile_mutex.synchronize do
415
+ # Any thread holding this lock will be compiling the template needed
416
+ # by the threads waiting. So re-check the @compiled flag to avoid
417
+ # re-compilation
418
+ return if @compiled
419
+
420
+ mod = view.compiled_method_container
421
+
422
+ instrument("!compile_template") do
423
+ compile(mod)
424
+ end
425
+
426
+ @compiled = true
427
+ end
428
+ end
429
+
430
+ # This method compiles the source of the template. The compilation of templates
431
+ # involves setting strict_locals! if applicable, encoding the template, and setting
432
+ # frozen string literal.
433
+ def compiled_source
434
+ set_strict_locals = strict_locals!
435
+ source = encode!
436
+ code = @handler.call(self, source)
437
+
438
+ method_arguments =
439
+ if set_strict_locals
440
+ if set_strict_locals.include?("&")
441
+ "local_assigns, output_buffer, #{set_strict_locals}"
442
+ else
443
+ "local_assigns, output_buffer, #{set_strict_locals}, &_"
444
+ end
445
+ else
446
+ "local_assigns, output_buffer, &_"
447
+ end
448
+
449
+ # Make sure that the resulting String to be eval'd is in the
450
+ # encoding of the code
451
+ source = +<<-end_src
452
+ def #{method_name}(#{method_arguments})
453
+ @virtual_path = #{@virtual_path.inspect};#{locals_code};#{code}
454
+ end
455
+ end_src
456
+
457
+ # Make sure the source is in the encoding of the returned code
458
+ source.force_encoding(code.encoding)
459
+
460
+ # In case we get back a String from a handler that is not in
461
+ # BINARY or the default_internal, encode it to the default_internal
462
+ source.encode!
463
+
464
+ # Now, validate that the source we got back from the template
465
+ # handler is valid in the default_internal. This is for handlers
466
+ # that handle encoding but screw up
467
+ unless source.valid_encoding?
468
+ raise WrongEncodingError.new(source, Encoding.default_internal)
469
+ end
470
+
471
+ if Template.frozen_string_literal
472
+ "# frozen_string_literal: true\n#{source}"
473
+ else
474
+ source
475
+ end
476
+ end
477
+
478
+ # Among other things, this method is responsible for properly setting
479
+ # the encoding of the compiled template.
480
+ #
481
+ # If the template engine handles encodings, we send the encoded
482
+ # String to the engine without further processing. This allows
483
+ # the template engine to support additional mechanisms for
484
+ # specifying the encoding. For instance, ERB supports <%# encoding: %>
485
+ #
486
+ # Otherwise, after we figure out the correct encoding, we then
487
+ # encode the source into <tt>Encoding.default_internal</tt>.
488
+ # In general, this means that templates will be UTF-8 inside of Rails,
489
+ # regardless of the original source encoding.
490
+ def compile(mod)
491
+ begin
492
+ mod.module_eval(compiled_source, identifier, offset)
493
+ rescue SyntaxError
494
+ # Account for when code in the template is not syntactically valid; e.g. if we're using
495
+ # ERB and the user writes <%= foo( %>, attempting to call a helper `foo` and interpolate
496
+ # the result into the template, but missing an end parenthesis.
497
+ raise SyntaxErrorInTemplate.new(self, encode!)
498
+ end
499
+
500
+ return unless strict_locals?
501
+
502
+ parameters = mod.instance_method(method_name).parameters
503
+ parameters -= [[:req, :local_assigns], [:req, :output_buffer]]
504
+
505
+ # Check compiled method parameters to ensure that only kwargs
506
+ # were provided as strict locals, preventing `locals: (foo, *foo)` etc
507
+ # and allowing `locals: (foo:)`.
508
+ non_kwarg_parameters = parameters.select do |parameter|
509
+ ![:keyreq, :key, :keyrest, :nokey].include?(parameter[0])
510
+ end
511
+
512
+ non_kwarg_parameters.pop if non_kwarg_parameters.last == %i(block _)
513
+
514
+ unless non_kwarg_parameters.empty?
515
+ mod.undef_method(method_name)
516
+
517
+ raise ArgumentError.new(
518
+ "#{non_kwarg_parameters.map { |_, name| "`#{name}`" }.to_sentence} set as non-keyword " \
519
+ "#{'argument'.pluralize(non_kwarg_parameters.length)} for #{short_identifier}. " \
520
+ "Locals can only be set as keyword arguments."
521
+ )
522
+ end
523
+
524
+ unless parameters.any? { |type, _| type == :keyrest }
525
+ parameters.map!(&:last)
526
+ parameters.sort!
527
+ @strict_local_keys = parameters.freeze
528
+ end
529
+ end
530
+
531
+ def offset
532
+ if Template.frozen_string_literal
533
+ -1
534
+ else
535
+ 0
536
+ end
537
+ end
538
+
539
+ def handle_render_error(view, e)
540
+ if e.is_a?(Template::Error)
541
+ e.sub_template_of(self)
542
+ raise e
543
+ else
544
+ raise Template::Error.new(self)
545
+ end
546
+ end
547
+
548
+ RUBY_RESERVED_KEYWORDS = ::ActiveSupport::Delegation::RUBY_RESERVED_KEYWORDS
549
+ private_constant :RUBY_RESERVED_KEYWORDS
550
+
551
+ def locals_code
552
+ return "" if strict_locals?
553
+
554
+ # Only locals with valid variable names get set directly. Others will
555
+ # still be available in local_assigns.
556
+ locals = @locals - RUBY_RESERVED_KEYWORDS
557
+
558
+ locals = locals.grep(/\A(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/)
559
+
560
+ # Assign for the same variable is to suppress unused variable warning
561
+ locals.each_with_object(+"") { |key, code| code << "#{key} = local_assigns[:#{key}]; #{key} = #{key};" }
562
+ end
563
+
564
+ def identifier_method_name
565
+ short_identifier.tr("^a-z_", "_")
566
+ end
567
+
568
+ def instrument(action, &block) # :doc:
569
+ ActiveSupport::Notifications.instrument("#{action}.action_view", instrument_payload, &block)
570
+ end
571
+
572
+ def instrument_render_template(&block)
573
+ ActiveSupport::Notifications.instrument("!render_template.action_view", instrument_payload, &block)
574
+ end
575
+
576
+ def instrument_payload
577
+ { virtual_path: @virtual_path, identifier: @identifier }
578
+ end
579
+ end
580
+ end
@@ -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,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ # = Action View \TemplatePath
5
+ #
6
+ # Represents a template path within ActionView's lookup and rendering system,
7
+ # like "users/show"
8
+ #
9
+ # TemplatePath makes it convenient to convert between separate name, prefix,
10
+ # partial arguments and the virtual path.
11
+ class TemplatePath
12
+ attr_reader :name, :prefix, :partial, :virtual
13
+ alias_method :partial?, :partial
14
+ alias_method :virtual_path, :virtual
15
+
16
+ # Convert name, prefix, and partial into a virtual path string
17
+ def self.virtual(name, prefix, partial)
18
+ if prefix.empty?
19
+ "#{partial ? "_" : ""}#{name}"
20
+ elsif partial
21
+ "#{prefix}/_#{name}"
22
+ else
23
+ "#{prefix}/#{name}"
24
+ end
25
+ end
26
+
27
+ # Build a TemplatePath form a virtual path
28
+ def self.parse(virtual)
29
+ if nameidx = virtual.rindex("/")
30
+ prefix = virtual[0, nameidx]
31
+ name = virtual.from(nameidx + 1)
32
+ prefix = prefix[1..] if prefix.start_with?("/")
33
+ else
34
+ prefix = ""
35
+ name = virtual
36
+ end
37
+ partial = name.start_with?("_")
38
+ name = name[1..] if partial
39
+ new name, prefix, partial, virtual
40
+ end
41
+
42
+ # Convert name, prefix, and partial into a TemplatePath
43
+ def self.build(name, prefix, partial)
44
+ new name, prefix, partial, virtual(name, prefix, partial)
45
+ end
46
+
47
+ def initialize(name, prefix, partial, virtual)
48
+ @name = name
49
+ @prefix = prefix
50
+ @partial = partial
51
+ @virtual = virtual
52
+ end
53
+
54
+ alias :to_str :virtual
55
+ alias :to_s :virtual
56
+
57
+ def hash # :nodoc:
58
+ @virtual.hash
59
+ end
60
+
61
+ def eql?(other) # :nodoc:
62
+ @virtual == other.virtual
63
+ end
64
+ alias :== :eql? # :nodoc:
65
+ end
66
+ end