actionview 7.1.5.1 → 7.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +100 -416
- data/README.rdoc +1 -1
- data/lib/action_view/base.rb +24 -9
- data/lib/action_view/cache_expiry.rb +9 -3
- data/lib/action_view/dependency_tracker/{ripper_tracker.rb → ruby_tracker.rb} +4 -3
- data/lib/action_view/dependency_tracker.rb +1 -1
- data/lib/action_view/digestor.rb +6 -2
- data/lib/action_view/gem_version.rb +3 -3
- data/lib/action_view/helpers/asset_tag_helper.rb +19 -7
- data/lib/action_view/helpers/atom_feed_helper.rb +1 -1
- data/lib/action_view/helpers/cache_helper.rb +2 -2
- data/lib/action_view/helpers/csrf_helper.rb +1 -1
- data/lib/action_view/helpers/date_helper.rb +8 -1
- data/lib/action_view/helpers/form_helper.rb +222 -217
- data/lib/action_view/helpers/form_options_helper.rb +6 -3
- data/lib/action_view/helpers/form_tag_helper.rb +80 -47
- data/lib/action_view/helpers/output_safety_helper.rb +5 -6
- data/lib/action_view/helpers/tag_helper.rb +208 -18
- data/lib/action_view/helpers/tags/collection_helpers.rb +2 -1
- data/lib/action_view/helpers/text_helper.rb +11 -4
- data/lib/action_view/helpers/url_helper.rb +3 -77
- data/lib/action_view/layouts.rb +8 -10
- data/lib/action_view/log_subscriber.rb +8 -4
- data/lib/action_view/railtie.rb +0 -1
- data/lib/action_view/render_parser/prism_render_parser.rb +127 -0
- data/lib/action_view/{ripper_ast_parser.rb → render_parser/ripper_render_parser.rb} +152 -9
- data/lib/action_view/render_parser.rb +21 -169
- data/lib/action_view/renderer/abstract_renderer.rb +1 -1
- data/lib/action_view/renderer/partial_renderer.rb +2 -2
- data/lib/action_view/renderer/renderer.rb +32 -38
- data/lib/action_view/renderer/template_renderer.rb +3 -3
- data/lib/action_view/rendering.rb +4 -4
- data/lib/action_view/template/error.rb +11 -0
- data/lib/action_view/template/handlers/erb.rb +45 -37
- data/lib/action_view/template/renderable.rb +7 -1
- data/lib/action_view/template/resolver.rb +0 -2
- data/lib/action_view/template.rb +36 -8
- data/lib/action_view/test_case.rb +7 -10
- data/lib/action_view.rb +1 -0
- metadata +30 -18
|
@@ -195,42 +195,6 @@ module ActionView
|
|
|
195
195
|
# link_to "Visit Other Site", "https://rubyonrails.org/", data: { turbo_confirm: "Are you sure?" }
|
|
196
196
|
# # => <a href="https://rubyonrails.org/" data-turbo-confirm="Are you sure?">Visit Other Site</a>
|
|
197
197
|
#
|
|
198
|
-
# ==== Deprecated: \Rails UJS Attributes
|
|
199
|
-
#
|
|
200
|
-
# Prior to \Rails 7, \Rails shipped with a JavaScript library called <tt>@rails/ujs</tt> on by default. Following \Rails 7,
|
|
201
|
-
# this library is no longer on by default. This library integrated with the following options:
|
|
202
|
-
#
|
|
203
|
-
# * <tt>method: symbol of HTTP verb</tt> - This modifier will dynamically
|
|
204
|
-
# create an HTML form and immediately submit the form for processing using
|
|
205
|
-
# the HTTP verb specified. Useful for having links perform a POST operation
|
|
206
|
-
# in dangerous actions like deleting a record (which search bots can follow
|
|
207
|
-
# while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>.
|
|
208
|
-
# Note that if the user has JavaScript disabled, the request will fall back
|
|
209
|
-
# to using GET. If <tt>href: '#'</tt> is used and the user has JavaScript
|
|
210
|
-
# disabled clicking the link will have no effect. If you are relying on the
|
|
211
|
-
# POST behavior, you should check for it in your controller's action by using
|
|
212
|
-
# the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>patch?</tt>, or <tt>put?</tt>.
|
|
213
|
-
# * <tt>remote: true</tt> - This will allow <tt>@rails/ujs</tt>
|
|
214
|
-
# to make an Ajax request to the URL in question instead of following
|
|
215
|
-
# the link.
|
|
216
|
-
#
|
|
217
|
-
# <tt>@rails/ujs</tt> also integrated with the following +:data+ options:
|
|
218
|
-
#
|
|
219
|
-
# * <tt>confirm: "question?"</tt> - This will allow <tt>@rails/ujs</tt>
|
|
220
|
-
# to prompt with the question specified (in this case, the
|
|
221
|
-
# resulting text would be <tt>question?</tt>). If the user accepts, the
|
|
222
|
-
# link is processed normally, otherwise no action is taken.
|
|
223
|
-
# * <tt>:disable_with</tt> - Value of this parameter will be used as the
|
|
224
|
-
# name for a disabled version of the link.
|
|
225
|
-
#
|
|
226
|
-
# ===== \Rails UJS Examples
|
|
227
|
-
#
|
|
228
|
-
# link_to "Remove Profile", profile_path(@profile), method: :delete
|
|
229
|
-
# # => <a href="/profiles/1" rel="nofollow" data-method="delete">Remove Profile</a>
|
|
230
|
-
#
|
|
231
|
-
# link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" }
|
|
232
|
-
# # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?">Visit Other Site</a>
|
|
233
|
-
#
|
|
234
198
|
def link_to(name = nil, options = nil, html_options = nil, &block)
|
|
235
199
|
html_options, options, name = options, name, block if block_given?
|
|
236
200
|
options ||= {}
|
|
@@ -256,8 +220,9 @@ module ActionView
|
|
|
256
220
|
# +:form_class+ option within +html_options+. It defaults to
|
|
257
221
|
# <tt>"button_to"</tt> to allow styling of the form and its children.
|
|
258
222
|
#
|
|
259
|
-
# The form submits a POST request by default
|
|
260
|
-
#
|
|
223
|
+
# The form submits a POST request by default if the object is not persisted;
|
|
224
|
+
# conversely, if the object is persisted, it will submit a PATCH request.
|
|
225
|
+
# To specify a different HTTP verb use the +:method+ option within +html_options+.
|
|
261
226
|
#
|
|
262
227
|
# If the HTML button generated from +button_to+ does not work with your layout, you can
|
|
263
228
|
# consider using the +link_to+ method with the +data-turbo-method+
|
|
@@ -328,32 +293,6 @@ module ActionView
|
|
|
328
293
|
# # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6" autocomplete="off"/>
|
|
329
294
|
# # </form>"
|
|
330
295
|
#
|
|
331
|
-
# ==== Deprecated: \Rails UJS Attributes
|
|
332
|
-
#
|
|
333
|
-
# Prior to \Rails 7, \Rails shipped with a JavaScript library called <tt>@rails/ujs</tt> on by default. Following \Rails 7,
|
|
334
|
-
# this library is no longer on by default. This library integrated with the following options:
|
|
335
|
-
#
|
|
336
|
-
# * <tt>:remote</tt> - If set to true, will allow <tt>@rails/ujs</tt> to control the
|
|
337
|
-
# submit behavior. By default this behavior is an Ajax submit.
|
|
338
|
-
#
|
|
339
|
-
# <tt>@rails/ujs</tt> also integrated with the following +:data+ options:
|
|
340
|
-
#
|
|
341
|
-
# * <tt>confirm: "question?"</tt> - This will allow <tt>@rails/ujs</tt>
|
|
342
|
-
# to prompt with the question specified (in this case, the
|
|
343
|
-
# resulting text would be <tt>question?</tt>). If the user accepts, the
|
|
344
|
-
# button is processed normally, otherwise no action is taken.
|
|
345
|
-
# * <tt>:disable_with</tt> - Value of this parameter will be
|
|
346
|
-
# used as the value for a disabled version of the submit
|
|
347
|
-
# button when the form is submitted.
|
|
348
|
-
#
|
|
349
|
-
# ===== \Rails UJS Examples
|
|
350
|
-
#
|
|
351
|
-
# <%= button_to "Create", { action: "create" }, remote: true, form: { "data-type" => "json" } %>
|
|
352
|
-
# # => "<form method="post" action="/images/create" class="button_to" data-remote="true" data-type="json">
|
|
353
|
-
# # <button type="submit">Create</button>
|
|
354
|
-
# # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6" autocomplete="off"/>
|
|
355
|
-
# # </form>"
|
|
356
|
-
#
|
|
357
296
|
def button_to(name = nil, options = nil, html_options = nil, &block)
|
|
358
297
|
html_options, options = options, name if block_given?
|
|
359
298
|
html_options ||= {}
|
|
@@ -638,19 +577,6 @@ module ActionView
|
|
|
638
577
|
url_string == request_uri
|
|
639
578
|
end
|
|
640
579
|
|
|
641
|
-
if RUBY_VERSION.start_with?("2.7")
|
|
642
|
-
using Module.new {
|
|
643
|
-
refine UrlHelper do
|
|
644
|
-
alias :_current_page? :current_page?
|
|
645
|
-
end
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
def current_page?(*args) # :nodoc:
|
|
649
|
-
options = args.pop
|
|
650
|
-
options.is_a?(Hash) ? _current_page?(*args, **options) : _current_page?(*args, options)
|
|
651
|
-
end
|
|
652
|
-
end
|
|
653
|
-
|
|
654
580
|
# Creates an SMS anchor link tag to the specified +phone_number+. When the
|
|
655
581
|
# link is clicked, the default SMS messaging app is opened ready to send a
|
|
656
582
|
# message to the linked phone number. If the +body+ option is specified,
|
data/lib/action_view/layouts.rb
CHANGED
|
@@ -209,11 +209,9 @@ module ActionView
|
|
|
209
209
|
|
|
210
210
|
included do
|
|
211
211
|
class_attribute :_layout, instance_accessor: false
|
|
212
|
-
class_attribute :_layout_conditions, instance_accessor: false, default: {}
|
|
212
|
+
class_attribute :_layout_conditions, instance_accessor: false, instance_reader: true, default: {}
|
|
213
213
|
|
|
214
214
|
_write_layout_method
|
|
215
|
-
|
|
216
|
-
delegate :_layout_conditions, to: :class
|
|
217
215
|
end
|
|
218
216
|
|
|
219
217
|
module ClassMethods
|
|
@@ -286,7 +284,7 @@ module ActionView
|
|
|
286
284
|
silence_redefinition_of_method(:_layout)
|
|
287
285
|
|
|
288
286
|
prefixes = /\blayouts/.match?(_implied_layout_name) ? [] : ["layouts"]
|
|
289
|
-
default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}, false,
|
|
287
|
+
default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}, false, keys, { formats: formats }).first || super"
|
|
290
288
|
name_clause = if name
|
|
291
289
|
default_behavior
|
|
292
290
|
else
|
|
@@ -327,7 +325,7 @@ module ActionView
|
|
|
327
325
|
|
|
328
326
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
329
327
|
# frozen_string_literal: true
|
|
330
|
-
def _layout(lookup_context, formats)
|
|
328
|
+
def _layout(lookup_context, formats, keys)
|
|
331
329
|
if _conditional_layout?
|
|
332
330
|
#{layout_definition}
|
|
333
331
|
else
|
|
@@ -391,8 +389,8 @@ module ActionView
|
|
|
391
389
|
case name
|
|
392
390
|
when String then _normalize_layout(name)
|
|
393
391
|
when Proc then name
|
|
394
|
-
when true then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, true) }
|
|
395
|
-
when :default then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, false) }
|
|
392
|
+
when true then Proc.new { |lookup_context, formats, keys| _default_layout(lookup_context, formats, keys, true) }
|
|
393
|
+
when :default then Proc.new { |lookup_context, formats, keys| _default_layout(lookup_context, formats, keys, false) }
|
|
396
394
|
when false, nil then nil
|
|
397
395
|
else
|
|
398
396
|
raise ArgumentError,
|
|
@@ -414,9 +412,9 @@ module ActionView
|
|
|
414
412
|
#
|
|
415
413
|
# ==== Returns
|
|
416
414
|
# * <tt>template</tt> - The template object for the default layout (or +nil+)
|
|
417
|
-
def _default_layout(lookup_context, formats, require_layout = false)
|
|
415
|
+
def _default_layout(lookup_context, formats, keys, require_layout = false)
|
|
418
416
|
begin
|
|
419
|
-
value = _layout(lookup_context, formats) if action_has_layout?
|
|
417
|
+
value = _layout(lookup_context, formats, keys) if action_has_layout?
|
|
420
418
|
rescue NameError => e
|
|
421
419
|
raise e, "Could not render layout: #{e.message}"
|
|
422
420
|
end
|
|
@@ -430,7 +428,7 @@ module ActionView
|
|
|
430
428
|
end
|
|
431
429
|
|
|
432
430
|
def _include_layout?(options)
|
|
433
|
-
|
|
431
|
+
!options.keys.intersect?([:body, :plain, :html, :inline, :partial]) || options.key?(:layout)
|
|
434
432
|
end
|
|
435
433
|
end
|
|
436
434
|
end
|
|
@@ -18,7 +18,7 @@ module ActionView
|
|
|
18
18
|
info do
|
|
19
19
|
message = +" Rendered #{from_rails_root(event.payload[:identifier])}"
|
|
20
20
|
message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
|
|
21
|
-
message << " (Duration: #{event.duration.round(1)}ms |
|
|
21
|
+
message << " (Duration: #{event.duration.round(1)}ms | GC: #{event.gc_time.round(1)}ms)"
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
subscribe_log_level :render_template, :debug
|
|
@@ -27,7 +27,7 @@ module ActionView
|
|
|
27
27
|
debug do
|
|
28
28
|
message = +" Rendered #{from_rails_root(event.payload[:identifier])}"
|
|
29
29
|
message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
|
|
30
|
-
message << " (Duration: #{event.duration.round(1)}ms |
|
|
30
|
+
message << " (Duration: #{event.duration.round(1)}ms | GC: #{event.gc_time.round(1)}ms)"
|
|
31
31
|
message << " #{cache_message(event.payload)}" unless event.payload[:cache_hit].nil?
|
|
32
32
|
message
|
|
33
33
|
end
|
|
@@ -37,7 +37,7 @@ module ActionView
|
|
|
37
37
|
def render_layout(event)
|
|
38
38
|
info do
|
|
39
39
|
message = +" Rendered layout #{from_rails_root(event.payload[:identifier])}"
|
|
40
|
-
message << " (Duration: #{event.duration.round(1)}ms |
|
|
40
|
+
message << " (Duration: #{event.duration.round(1)}ms | GC: #{event.gc_time.round(1)}ms)"
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
subscribe_log_level :render_layout, :info
|
|
@@ -48,7 +48,7 @@ module ActionView
|
|
|
48
48
|
debug do
|
|
49
49
|
message = +" Rendered collection of #{from_rails_root(identifier)}"
|
|
50
50
|
message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
|
|
51
|
-
message << " #{render_count(event.payload)} (Duration: #{event.duration.round(1)}ms |
|
|
51
|
+
message << " #{render_count(event.payload)} (Duration: #{event.duration.round(1)}ms | GC: #{event.gc_time.round(1)}ms)"
|
|
52
52
|
message
|
|
53
53
|
end
|
|
54
54
|
end
|
|
@@ -96,6 +96,10 @@ module ActionView
|
|
|
96
96
|
|
|
97
97
|
def finish(name, id, payload)
|
|
98
98
|
end
|
|
99
|
+
|
|
100
|
+
def silenced?(_)
|
|
101
|
+
logger.nil? || !logger.debug?
|
|
102
|
+
end
|
|
99
103
|
end
|
|
100
104
|
|
|
101
105
|
def self.attach_to(*)
|
data/lib/action_view/railtie.rb
CHANGED
|
@@ -116,7 +116,6 @@ module ActionView
|
|
|
116
116
|
view_reloader = ActionView::CacheExpiry::ViewReloader.new(watcher: app.config.file_watcher)
|
|
117
117
|
|
|
118
118
|
app.reloaders << view_reloader
|
|
119
|
-
view_reloader.execute
|
|
120
119
|
app.reloader.to_run do
|
|
121
120
|
require_unload_lock!
|
|
122
121
|
view_reloader.execute
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionView
|
|
4
|
+
module RenderParser
|
|
5
|
+
class PrismRenderParser < Base # :nodoc:
|
|
6
|
+
def render_calls
|
|
7
|
+
queue = [Prism.parse(@code).value]
|
|
8
|
+
templates = []
|
|
9
|
+
|
|
10
|
+
while (node = queue.shift)
|
|
11
|
+
queue.concat(node.compact_child_nodes)
|
|
12
|
+
next unless node.is_a?(Prism::CallNode)
|
|
13
|
+
|
|
14
|
+
options = render_call_options(node)
|
|
15
|
+
next unless options
|
|
16
|
+
|
|
17
|
+
render_type = (options.keys & RENDER_TYPE_KEYS)[0]
|
|
18
|
+
template, object_template = render_call_template(options[render_type])
|
|
19
|
+
next unless template
|
|
20
|
+
|
|
21
|
+
if options.key?(:object) || options.key?(:collection) || object_template
|
|
22
|
+
next if options.key?(:object) && options.key?(:collection)
|
|
23
|
+
next unless options.key?(:partial)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if options[:spacer_template].is_a?(Prism::StringNode)
|
|
27
|
+
templates << partial_to_virtual_path(:partial, options[:spacer_template].unescaped)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
templates << partial_to_virtual_path(render_type, template)
|
|
31
|
+
|
|
32
|
+
if render_type != :layout && options[:layout].is_a?(Prism::StringNode)
|
|
33
|
+
templates << partial_to_virtual_path(:layout, options[:layout].unescaped)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
templates
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
# Accept a call node and return a hash of options for the render call.
|
|
42
|
+
# If it doesn't match the expected format, return nil.
|
|
43
|
+
def render_call_options(node)
|
|
44
|
+
# We are only looking for calls to render or render_to_string.
|
|
45
|
+
name = node.name.to_sym
|
|
46
|
+
return if name != :render && name != :render_to_string
|
|
47
|
+
|
|
48
|
+
# We are only looking for calls with arguments.
|
|
49
|
+
arguments = node.arguments
|
|
50
|
+
return unless arguments
|
|
51
|
+
|
|
52
|
+
arguments = arguments.arguments
|
|
53
|
+
length = arguments.length
|
|
54
|
+
|
|
55
|
+
# Get rid of any parentheses to get directly to the contents.
|
|
56
|
+
arguments.map! do |argument|
|
|
57
|
+
current = argument
|
|
58
|
+
|
|
59
|
+
while current.is_a?(Prism::ParenthesesNode) &&
|
|
60
|
+
current.body.is_a?(Prism::StatementsNode) &&
|
|
61
|
+
current.body.body.length == 1
|
|
62
|
+
current = current.body.body.first
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
current
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# We are only looking for arguments that are either a string with an
|
|
69
|
+
# array of locals or a keyword hash with symbol keys.
|
|
70
|
+
options =
|
|
71
|
+
if (length == 1 || length == 2) && !arguments[0].is_a?(Prism::KeywordHashNode)
|
|
72
|
+
{ partial: arguments[0], locals: arguments[1] }
|
|
73
|
+
elsif length == 1 &&
|
|
74
|
+
arguments[0].is_a?(Prism::KeywordHashNode) &&
|
|
75
|
+
arguments[0].elements.all? do |element|
|
|
76
|
+
element.is_a?(Prism::AssocNode) && element.key.is_a?(Prism::SymbolNode)
|
|
77
|
+
end
|
|
78
|
+
arguments[0].elements.to_h do |element|
|
|
79
|
+
[element.key.unescaped.to_sym, element.value]
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
return unless options
|
|
84
|
+
|
|
85
|
+
# Here we validate that the options have the keys we expect.
|
|
86
|
+
keys = options.keys
|
|
87
|
+
return if !keys.intersect?(RENDER_TYPE_KEYS)
|
|
88
|
+
return if (keys - ALL_KNOWN_KEYS).any?
|
|
89
|
+
|
|
90
|
+
# Finally, we can return a valid set of options.
|
|
91
|
+
options
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Accept the node that is being passed in the position of the template
|
|
95
|
+
# and return the template name and whether or not it is an object
|
|
96
|
+
# template.
|
|
97
|
+
def render_call_template(node)
|
|
98
|
+
object_template = false
|
|
99
|
+
template =
|
|
100
|
+
if node.is_a?(Prism::StringNode)
|
|
101
|
+
path = node.unescaped
|
|
102
|
+
path.include?("/") ? path : "#{directory}/#{path}"
|
|
103
|
+
else
|
|
104
|
+
dependency =
|
|
105
|
+
case node.type
|
|
106
|
+
when :class_variable_read_node
|
|
107
|
+
node.slice[2..]
|
|
108
|
+
when :instance_variable_read_node
|
|
109
|
+
node.slice[1..]
|
|
110
|
+
when :global_variable_read_node
|
|
111
|
+
node.slice[1..]
|
|
112
|
+
when :local_variable_read_node
|
|
113
|
+
node.slice
|
|
114
|
+
when :call_node
|
|
115
|
+
node.name.to_s
|
|
116
|
+
else
|
|
117
|
+
return
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
"#{dependency.pluralize}/#{dependency.singularize}"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
[template, object_template]
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "ripper"
|
|
4
|
-
|
|
5
3
|
module ActionView
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
module RenderParser
|
|
5
|
+
class RipperRenderParser < Base # :nodoc:
|
|
8
6
|
class Node < ::Array # :nodoc:
|
|
9
7
|
attr_reader :type
|
|
10
8
|
|
|
@@ -183,16 +181,161 @@ module ActionView
|
|
|
183
181
|
end
|
|
184
182
|
end
|
|
185
183
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
def parse_render_nodes(code)
|
|
189
|
-
parser = RenderCallExtractor.new(code)
|
|
184
|
+
def render_calls
|
|
185
|
+
parser = RenderCallExtractor.new(@code)
|
|
190
186
|
parser.parse
|
|
191
187
|
|
|
192
188
|
parser.render_calls.group_by(&:first).to_h do |method, nodes|
|
|
193
189
|
[ method.to_sym, nodes.collect { |v| v[1] } ]
|
|
194
|
-
end
|
|
190
|
+
end.map do |method, nodes|
|
|
191
|
+
nodes.map { |n| parse_render(n) }
|
|
192
|
+
end.flatten.compact
|
|
195
193
|
end
|
|
194
|
+
|
|
195
|
+
private
|
|
196
|
+
def resolve_path_directory(path)
|
|
197
|
+
if path.include?("/")
|
|
198
|
+
path
|
|
199
|
+
else
|
|
200
|
+
"#{directory}/#{path}"
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Convert
|
|
205
|
+
# render("foo", ...)
|
|
206
|
+
# into either
|
|
207
|
+
# render(template: "foo", ...)
|
|
208
|
+
# or
|
|
209
|
+
# render(partial: "foo", ...)
|
|
210
|
+
def normalize_args(string, options_hash)
|
|
211
|
+
if options_hash
|
|
212
|
+
{ partial: string, locals: options_hash }
|
|
213
|
+
else
|
|
214
|
+
{ partial: string }
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def parse_render(node)
|
|
219
|
+
node = node.argument_nodes
|
|
220
|
+
|
|
221
|
+
if (node.length == 1 || node.length == 2) && !node[0].hash?
|
|
222
|
+
if node.length == 1
|
|
223
|
+
options = normalize_args(node[0], nil)
|
|
224
|
+
elsif node.length == 2
|
|
225
|
+
options = normalize_args(node[0], node[1])
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
return nil unless options
|
|
229
|
+
|
|
230
|
+
parse_render_from_options(options)
|
|
231
|
+
elsif node.length == 1 && node[0].hash?
|
|
232
|
+
options = parse_hash_to_symbols(node[0])
|
|
233
|
+
|
|
234
|
+
return nil unless options
|
|
235
|
+
|
|
236
|
+
parse_render_from_options(options)
|
|
237
|
+
else
|
|
238
|
+
nil
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def parse_hash(node)
|
|
243
|
+
node.hash? && node.to_hash
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def parse_hash_to_symbols(node)
|
|
247
|
+
hash = parse_hash(node)
|
|
248
|
+
|
|
249
|
+
return unless hash
|
|
250
|
+
|
|
251
|
+
hash.transform_keys do |key_node|
|
|
252
|
+
key = parse_sym(key_node)
|
|
253
|
+
|
|
254
|
+
return unless key
|
|
255
|
+
|
|
256
|
+
key
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def parse_render_from_options(options_hash)
|
|
261
|
+
renders = []
|
|
262
|
+
keys = options_hash.keys
|
|
263
|
+
|
|
264
|
+
if (keys & RENDER_TYPE_KEYS).size < 1
|
|
265
|
+
# Must have at least one of render keys
|
|
266
|
+
return nil
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
if (keys - ALL_KNOWN_KEYS).any?
|
|
270
|
+
# de-opt in case of unknown option
|
|
271
|
+
return nil
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
render_type = (keys & RENDER_TYPE_KEYS)[0]
|
|
275
|
+
|
|
276
|
+
node = options_hash[render_type]
|
|
277
|
+
|
|
278
|
+
if node.string?
|
|
279
|
+
template = resolve_path_directory(node.to_string)
|
|
280
|
+
else
|
|
281
|
+
if node.variable_reference?
|
|
282
|
+
dependency = node.variable_name.sub(/\A(?:\$|@{1,2})/, "")
|
|
283
|
+
elsif node.vcall?
|
|
284
|
+
dependency = node.variable_name
|
|
285
|
+
elsif node.call?
|
|
286
|
+
dependency = node.call_method_name
|
|
287
|
+
else
|
|
288
|
+
return
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
object_template = true
|
|
292
|
+
template = "#{dependency.pluralize}/#{dependency.singularize}"
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
return unless template
|
|
296
|
+
|
|
297
|
+
if spacer_template = render_template_with_spacer?(options_hash)
|
|
298
|
+
virtual_path = partial_to_virtual_path(:partial, spacer_template)
|
|
299
|
+
renders << virtual_path
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
if options_hash.key?(:object) || options_hash.key?(:collection) || object_template
|
|
303
|
+
return nil if options_hash.key?(:object) && options_hash.key?(:collection)
|
|
304
|
+
return nil unless options_hash.key?(:partial)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
virtual_path = partial_to_virtual_path(render_type, template)
|
|
308
|
+
renders << virtual_path
|
|
309
|
+
|
|
310
|
+
# Support for rendering multiple templates (i.e. a partial with a layout)
|
|
311
|
+
if layout_template = render_template_with_layout?(render_type, options_hash)
|
|
312
|
+
virtual_path = partial_to_virtual_path(:layout, layout_template)
|
|
313
|
+
|
|
314
|
+
renders << virtual_path
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
renders
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def parse_str(node)
|
|
321
|
+
node.string? && node.to_string
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def parse_sym(node)
|
|
325
|
+
node.symbol? && node.to_symbol
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def render_template_with_layout?(render_type, options_hash)
|
|
329
|
+
if render_type != :layout && options_hash.key?(:layout)
|
|
330
|
+
parse_str(options_hash[:layout])
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def render_template_with_spacer?(options_hash)
|
|
335
|
+
if options_hash.key?(:spacer_template)
|
|
336
|
+
parse_str(options_hash[:spacer_template])
|
|
337
|
+
end
|
|
338
|
+
end
|
|
196
339
|
end
|
|
197
340
|
end
|
|
198
341
|
end
|