omg-actionview 8.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +25 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +40 -0
- data/app/assets/javascripts/rails-ujs.esm.js +686 -0
- data/app/assets/javascripts/rails-ujs.js +630 -0
- data/lib/action_view/base.rb +316 -0
- data/lib/action_view/buffers.rb +165 -0
- data/lib/action_view/cache_expiry.rb +69 -0
- data/lib/action_view/context.rb +32 -0
- data/lib/action_view/dependency_tracker/erb_tracker.rb +159 -0
- data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
- data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
- data/lib/action_view/dependency_tracker.rb +41 -0
- data/lib/action_view/deprecator.rb +7 -0
- data/lib/action_view/digestor.rb +130 -0
- data/lib/action_view/flows.rb +75 -0
- data/lib/action_view/gem_version.rb +17 -0
- data/lib/action_view/helpers/active_model_helper.rb +54 -0
- data/lib/action_view/helpers/asset_tag_helper.rb +680 -0
- data/lib/action_view/helpers/asset_url_helper.rb +473 -0
- data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
- data/lib/action_view/helpers/cache_helper.rb +315 -0
- data/lib/action_view/helpers/capture_helper.rb +236 -0
- data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
- data/lib/action_view/helpers/controller_helper.rb +42 -0
- data/lib/action_view/helpers/csp_helper.rb +26 -0
- data/lib/action_view/helpers/csrf_helper.rb +35 -0
- data/lib/action_view/helpers/date_helper.rb +1266 -0
- data/lib/action_view/helpers/debug_helper.rb +38 -0
- data/lib/action_view/helpers/form_helper.rb +2765 -0
- data/lib/action_view/helpers/form_options_helper.rb +927 -0
- data/lib/action_view/helpers/form_tag_helper.rb +1088 -0
- data/lib/action_view/helpers/javascript_helper.rb +96 -0
- data/lib/action_view/helpers/number_helper.rb +165 -0
- data/lib/action_view/helpers/output_safety_helper.rb +70 -0
- data/lib/action_view/helpers/rendering_helper.rb +218 -0
- data/lib/action_view/helpers/sanitize_helper.rb +201 -0
- data/lib/action_view/helpers/tag_helper.rb +621 -0
- data/lib/action_view/helpers/tags/base.rb +138 -0
- data/lib/action_view/helpers/tags/check_box.rb +65 -0
- data/lib/action_view/helpers/tags/checkable.rb +18 -0
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +37 -0
- data/lib/action_view/helpers/tags/collection_helpers.rb +118 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
- data/lib/action_view/helpers/tags/collection_select.rb +33 -0
- data/lib/action_view/helpers/tags/color_field.rb +26 -0
- data/lib/action_view/helpers/tags/date_field.rb +14 -0
- data/lib/action_view/helpers/tags/date_select.rb +75 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +39 -0
- data/lib/action_view/helpers/tags/datetime_local_field.rb +29 -0
- data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
- data/lib/action_view/helpers/tags/email_field.rb +10 -0
- data/lib/action_view/helpers/tags/file_field.rb +26 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +34 -0
- data/lib/action_view/helpers/tags/hidden_field.rb +14 -0
- data/lib/action_view/helpers/tags/label.rb +84 -0
- data/lib/action_view/helpers/tags/month_field.rb +14 -0
- data/lib/action_view/helpers/tags/number_field.rb +20 -0
- data/lib/action_view/helpers/tags/password_field.rb +14 -0
- data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
- data/lib/action_view/helpers/tags/radio_button.rb +32 -0
- data/lib/action_view/helpers/tags/range_field.rb +10 -0
- data/lib/action_view/helpers/tags/search_field.rb +27 -0
- data/lib/action_view/helpers/tags/select.rb +45 -0
- data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
- data/lib/action_view/helpers/tags/tel_field.rb +10 -0
- data/lib/action_view/helpers/tags/text_area.rb +24 -0
- data/lib/action_view/helpers/tags/text_field.rb +33 -0
- data/lib/action_view/helpers/tags/time_field.rb +23 -0
- data/lib/action_view/helpers/tags/time_select.rb +10 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +25 -0
- data/lib/action_view/helpers/tags/translator.rb +39 -0
- data/lib/action_view/helpers/tags/url_field.rb +10 -0
- data/lib/action_view/helpers/tags/week_field.rb +14 -0
- data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
- data/lib/action_view/helpers/tags.rb +47 -0
- data/lib/action_view/helpers/text_helper.rb +568 -0
- data/lib/action_view/helpers/translation_helper.rb +161 -0
- data/lib/action_view/helpers/url_helper.rb +812 -0
- data/lib/action_view/helpers.rb +68 -0
- data/lib/action_view/layouts.rb +434 -0
- data/lib/action_view/locale/en.yml +56 -0
- data/lib/action_view/log_subscriber.rb +132 -0
- data/lib/action_view/lookup_context.rb +299 -0
- data/lib/action_view/model_naming.rb +14 -0
- data/lib/action_view/path_registry.rb +57 -0
- data/lib/action_view/path_set.rb +84 -0
- data/lib/action_view/railtie.rb +132 -0
- data/lib/action_view/record_identifier.rb +118 -0
- data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
- data/lib/action_view/render_parser/ripper_render_parser.rb +350 -0
- data/lib/action_view/render_parser.rb +40 -0
- data/lib/action_view/renderer/abstract_renderer.rb +186 -0
- data/lib/action_view/renderer/collection_renderer.rb +204 -0
- data/lib/action_view/renderer/object_renderer.rb +34 -0
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +120 -0
- data/lib/action_view/renderer/partial_renderer.rb +267 -0
- data/lib/action_view/renderer/renderer.rb +107 -0
- data/lib/action_view/renderer/streaming_template_renderer.rb +107 -0
- data/lib/action_view/renderer/template_renderer.rb +115 -0
- data/lib/action_view/rendering.rb +190 -0
- data/lib/action_view/routing_url_for.rb +149 -0
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template/error.rb +264 -0
- data/lib/action_view/template/handlers/builder.rb +25 -0
- data/lib/action_view/template/handlers/erb/erubi.rb +85 -0
- data/lib/action_view/template/handlers/erb.rb +157 -0
- data/lib/action_view/template/handlers/html.rb +11 -0
- data/lib/action_view/template/handlers/raw.rb +11 -0
- data/lib/action_view/template/handlers.rb +66 -0
- data/lib/action_view/template/html.rb +33 -0
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +25 -0
- data/lib/action_view/template/renderable.rb +30 -0
- data/lib/action_view/template/resolver.rb +212 -0
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/text.rb +32 -0
- data/lib/action_view/template/types.rb +50 -0
- data/lib/action_view/template.rb +580 -0
- data/lib/action_view/template_details.rb +66 -0
- data/lib/action_view/template_path.rb +66 -0
- data/lib/action_view/test_case.rb +449 -0
- data/lib/action_view/testing/resolvers.rb +44 -0
- data/lib/action_view/unbound_template.rb +67 -0
- data/lib/action_view/version.rb +10 -0
- data/lib/action_view/view_paths.rb +117 -0
- data/lib/action_view.rb +104 -0
- 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
|