actionview 7.0.8.1 → 7.2.2.1

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.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -425
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  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 +52 -14
  8. data/lib/action_view/buffers.rb +106 -8
  9. data/lib/action_view/cache_expiry.rb +44 -41
  10. data/lib/action_view/context.rb +1 -1
  11. data/lib/action_view/dependency_tracker/{ripper_tracker.rb → ruby_tracker.rb} +4 -3
  12. data/lib/action_view/dependency_tracker.rb +1 -1
  13. data/lib/action_view/deprecator.rb +7 -0
  14. data/lib/action_view/digestor.rb +1 -1
  15. data/lib/action_view/gem_version.rb +3 -3
  16. data/lib/action_view/helpers/active_model_helper.rb +1 -1
  17. data/lib/action_view/helpers/asset_tag_helper.rb +151 -55
  18. data/lib/action_view/helpers/asset_url_helper.rb +6 -5
  19. data/lib/action_view/helpers/atom_feed_helper.rb +5 -5
  20. data/lib/action_view/helpers/cache_helper.rb +7 -13
  21. data/lib/action_view/helpers/capture_helper.rb +30 -10
  22. data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
  23. data/lib/action_view/helpers/controller_helper.rb +6 -0
  24. data/lib/action_view/helpers/csp_helper.rb +2 -2
  25. data/lib/action_view/helpers/csrf_helper.rb +3 -3
  26. data/lib/action_view/helpers/date_helper.rb +17 -19
  27. data/lib/action_view/helpers/debug_helper.rb +3 -3
  28. data/lib/action_view/helpers/form_helper.rb +248 -214
  29. data/lib/action_view/helpers/form_options_helper.rb +2 -1
  30. data/lib/action_view/helpers/form_tag_helper.rb +125 -58
  31. data/lib/action_view/helpers/javascript_helper.rb +1 -0
  32. data/lib/action_view/helpers/number_helper.rb +37 -330
  33. data/lib/action_view/helpers/output_safety_helper.rb +6 -6
  34. data/lib/action_view/helpers/rendering_helper.rb +1 -1
  35. data/lib/action_view/helpers/sanitize_helper.rb +51 -21
  36. data/lib/action_view/helpers/tag_helper.rb +210 -42
  37. data/lib/action_view/helpers/tags/base.rb +11 -52
  38. data/lib/action_view/helpers/tags/collection_check_boxes.rb +1 -0
  39. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -0
  40. data/lib/action_view/helpers/tags/collection_select.rb +3 -0
  41. data/lib/action_view/helpers/tags/date_field.rb +1 -1
  42. data/lib/action_view/helpers/tags/date_select.rb +2 -0
  43. data/lib/action_view/helpers/tags/datetime_field.rb +14 -6
  44. data/lib/action_view/helpers/tags/datetime_local_field.rb +11 -2
  45. data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -0
  46. data/lib/action_view/helpers/tags/month_field.rb +1 -1
  47. data/lib/action_view/helpers/tags/select.rb +3 -0
  48. data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
  49. data/lib/action_view/helpers/tags/time_field.rb +1 -1
  50. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -0
  51. data/lib/action_view/helpers/tags/week_field.rb +1 -1
  52. data/lib/action_view/helpers/tags/weekday_select.rb +3 -0
  53. data/lib/action_view/helpers/tags.rb +2 -0
  54. data/lib/action_view/helpers/text_helper.rb +157 -85
  55. data/lib/action_view/helpers/translation_helper.rb +3 -3
  56. data/lib/action_view/helpers/url_helper.rb +35 -80
  57. data/lib/action_view/helpers.rb +2 -0
  58. data/lib/action_view/layouts.rb +8 -8
  59. data/lib/action_view/log_subscriber.rb +57 -36
  60. data/lib/action_view/lookup_context.rb +29 -13
  61. data/lib/action_view/path_registry.rb +57 -0
  62. data/lib/action_view/path_set.rb +13 -14
  63. data/lib/action_view/railtie.rb +25 -3
  64. data/lib/action_view/record_identifier.rb +15 -8
  65. data/lib/action_view/render_parser/prism_render_parser.rb +127 -0
  66. data/lib/action_view/render_parser/ripper_render_parser.rb +341 -0
  67. data/lib/action_view/render_parser.rb +21 -169
  68. data/lib/action_view/renderer/abstract_renderer.rb +2 -2
  69. data/lib/action_view/renderer/collection_renderer.rb +10 -2
  70. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +2 -1
  71. data/lib/action_view/renderer/partial_renderer.rb +2 -1
  72. data/lib/action_view/renderer/renderer.rb +34 -38
  73. data/lib/action_view/renderer/streaming_template_renderer.rb +3 -2
  74. data/lib/action_view/renderer/template_renderer.rb +3 -2
  75. data/lib/action_view/rendering.rb +26 -8
  76. data/lib/action_view/template/error.rb +14 -1
  77. data/lib/action_view/template/handlers/builder.rb +4 -4
  78. data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
  79. data/lib/action_view/template/handlers/erb.rb +73 -1
  80. data/lib/action_view/template/handlers.rb +1 -1
  81. data/lib/action_view/template/html.rb +1 -1
  82. data/lib/action_view/template/raw_file.rb +1 -1
  83. data/lib/action_view/template/renderable.rb +8 -2
  84. data/lib/action_view/template/resolver.rb +9 -3
  85. data/lib/action_view/template/text.rb +1 -1
  86. data/lib/action_view/template/types.rb +25 -34
  87. data/lib/action_view/template.rb +278 -55
  88. data/lib/action_view/template_path.rb +2 -0
  89. data/lib/action_view/test_case.rb +181 -28
  90. data/lib/action_view/unbound_template.rb +17 -7
  91. data/lib/action_view/version.rb +1 -1
  92. data/lib/action_view/view_paths.rb +15 -24
  93. data/lib/action_view.rb +4 -1
  94. metadata +31 -31
  95. data/lib/action_view/ripper_ast_parser.rb +0 -198
  96. data/lib/assets/compiled/rails-ujs.js +0 -777
@@ -13,6 +13,7 @@ module ActionView
13
13
  config.action_view.image_loading = nil
14
14
  config.action_view.image_decoding = nil
15
15
  config.action_view.apply_stylesheet_media_default = true
16
+ config.action_view.prepend_content_exfiltration_prevention = false
16
17
 
17
18
  config.eager_load_namespaces << ActionView
18
19
 
@@ -40,6 +41,17 @@ module ActionView
40
41
  end
41
42
  end
42
43
 
44
+ config.after_initialize do |app|
45
+ prepend_content_exfiltration_prevention = app.config.action_view.delete(:prepend_content_exfiltration_prevention)
46
+ ActionView::Helpers::ContentExfiltrationPreventionHelper.prepend_content_exfiltration_prevention = prepend_content_exfiltration_prevention
47
+ end
48
+
49
+ config.after_initialize do |app|
50
+ if klass = app.config.action_view.delete(:sanitizer_vendor)
51
+ ActionView::Helpers::SanitizeHelper.sanitizer_vendor = klass
52
+ end
53
+ end
54
+
43
55
  config.after_initialize do |app|
44
56
  button_to_generates_button_tag = app.config.action_view.delete(:button_to_generates_button_tag)
45
57
  unless button_to_generates_button_tag.nil?
@@ -67,6 +79,10 @@ module ActionView
67
79
  end
68
80
  end
69
81
 
82
+ initializer "action_view.deprecator", before: :load_environment_config do |app|
83
+ app.deprecators[:action_view] = ActionView.deprecator
84
+ end
85
+
70
86
  initializer "action_view.logger" do
71
87
  ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
72
88
  end
@@ -74,7 +90,7 @@ module ActionView
74
90
  initializer "action_view.caching" do |app|
75
91
  ActiveSupport.on_load(:action_view) do
76
92
  if app.config.action_view.cache_template_loading.nil?
77
- ActionView::Resolver.caching = app.config.cache_classes
93
+ ActionView::Resolver.caching = !app.config.reloading_enabled?
78
94
  end
79
95
  end
80
96
  end
@@ -91,13 +107,19 @@ module ActionView
91
107
 
92
108
  config.after_initialize do |app|
93
109
  enable_caching = if app.config.action_view.cache_template_loading.nil?
94
- app.config.cache_classes
110
+ !app.config.reloading_enabled?
95
111
  else
96
112
  app.config.action_view.cache_template_loading
97
113
  end
98
114
 
99
115
  unless enable_caching
100
- app.executor.register_hook ActionView::CacheExpiry::Executor.new(watcher: app.config.file_watcher)
116
+ view_reloader = ActionView::CacheExpiry::ViewReloader.new(watcher: app.config.file_watcher)
117
+
118
+ app.reloaders << view_reloader
119
+ app.reloader.to_run do
120
+ require_unload_lock!
121
+ view_reloader.execute
122
+ end
101
123
  end
102
124
  end
103
125
 
@@ -4,6 +4,8 @@ require "active_support/core_ext/module"
4
4
  require "action_view/model_naming"
5
5
 
6
6
  module ActionView
7
+ # = Action View \Record \Identifier
8
+ #
7
9
  # RecordIdentifier encapsulates methods used by various ActionView helpers
8
10
  # to associate records with DOM elements.
9
11
  #
@@ -31,6 +33,8 @@ module ActionView
31
33
  # automatically generated, following naming conventions encapsulated by the
32
34
  # RecordIdentifier methods #dom_id and #dom_class:
33
35
  #
36
+ # dom_id(Post) # => "new_post"
37
+ # dom_class(Post) # => "post"
34
38
  # dom_id(Post.new) # => "new_post"
35
39
  # dom_class(Post.new) # => "post"
36
40
  # dom_id(Post.find 42) # => "post_42"
@@ -79,18 +83,21 @@ module ActionView
79
83
  # The DOM id convention is to use the singular form of an object or class with the id following an underscore.
80
84
  # If no id is found, prefix with "new_" instead.
81
85
  #
82
- # dom_id(Post.find(45)) # => "post_45"
83
- # dom_id(Post.new) # => "new_post"
86
+ # dom_id(Post.find(45)) # => "post_45"
87
+ # dom_id(Post) # => "new_post"
84
88
  #
85
89
  # If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
86
90
  #
87
91
  # dom_id(Post.find(45), :edit) # => "edit_post_45"
88
- # dom_id(Post.new, :custom) # => "custom_post"
89
- def dom_id(record, prefix = nil)
90
- if record_id = record_key_for_dom_id(record)
91
- "#{dom_class(record, prefix)}#{JOIN}#{record_id}"
92
+ # dom_id(Post, :custom) # => "custom_post"
93
+ def dom_id(record_or_class, prefix = nil)
94
+ raise ArgumentError, "dom_id must be passed a record_or_class as the first argument, you passed #{record_or_class.inspect}" unless record_or_class
95
+
96
+ record_id = record_key_for_dom_id(record_or_class) unless record_or_class.is_a?(Class)
97
+ if record_id
98
+ "#{dom_class(record_or_class, prefix)}#{JOIN}#{record_id}"
92
99
  else
93
- dom_class(record, prefix || NEW)
100
+ dom_class(record_or_class, prefix || NEW)
94
101
  end
95
102
  end
96
103
 
@@ -105,7 +112,7 @@ module ActionView
105
112
  # make sure yourself that your dom ids are valid, in case you override this method.
106
113
  def record_key_for_dom_id(record) # :doc:
107
114
  key = convert_to_model(record).to_key
108
- key ? key.join(JOIN) : key
115
+ key && key.all? ? key.join(JOIN) : nil
109
116
  end
110
117
  end
111
118
  end
@@ -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
@@ -0,0 +1,341 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module RenderParser
5
+ class RipperRenderParser < Base # :nodoc:
6
+ class Node < ::Array # :nodoc:
7
+ attr_reader :type
8
+
9
+ def initialize(type, arr, opts = {})
10
+ @type = type
11
+ super(arr)
12
+ end
13
+
14
+ def children
15
+ to_a
16
+ end
17
+
18
+ def inspect
19
+ typeinfo = type && type != :list ? ":" + type.to_s + ", " : ""
20
+ "s(" + typeinfo + map(&:inspect).join(", ") + ")"
21
+ end
22
+
23
+ def fcall?
24
+ type == :command || type == :fcall
25
+ end
26
+
27
+ def fcall_named?(name)
28
+ fcall? &&
29
+ self[0].type == :@ident &&
30
+ self[0][0] == name
31
+ end
32
+
33
+ def argument_nodes
34
+ raise unless fcall?
35
+ return [] if self[1].nil?
36
+ if self[1].last == false || self[1].last.type == :vcall
37
+ self[1][0...-1]
38
+ else
39
+ self[1][0..-1]
40
+ end
41
+ end
42
+
43
+ def string?
44
+ type == :string_literal
45
+ end
46
+
47
+ def variable_reference?
48
+ type == :var_ref
49
+ end
50
+
51
+ def vcall?
52
+ type == :vcall
53
+ end
54
+
55
+ def call?
56
+ type == :call
57
+ end
58
+
59
+ def variable_name
60
+ self[0][0]
61
+ end
62
+
63
+ def call_method_name
64
+ self[2].first
65
+ end
66
+
67
+ def to_string
68
+ raise unless string?
69
+ self[0][0][0]
70
+ end
71
+
72
+ def hash?
73
+ type == :bare_assoc_hash || type == :hash
74
+ end
75
+
76
+ def to_hash
77
+ if type == :bare_assoc_hash
78
+ hash_from_body(self[0])
79
+ elsif type == :hash && self[0] == nil
80
+ {}
81
+ elsif type == :hash && self[0].type == :assoclist_from_args
82
+ hash_from_body(self[0][0])
83
+ end
84
+ end
85
+
86
+ def hash_from_body(body)
87
+ body.to_h do |hash_node|
88
+ return nil if hash_node.type != :assoc_new
89
+
90
+ [hash_node[0], hash_node[1]]
91
+ end
92
+ end
93
+
94
+ def symbol?
95
+ type == :@label || type == :symbol_literal
96
+ end
97
+
98
+ def to_symbol
99
+ if type == :@label && self[0] =~ /\A(.+):\z/
100
+ $1.to_sym
101
+ elsif type == :symbol_literal && self[0].type == :symbol && self[0][0].type == :@ident
102
+ self[0][0][0].to_sym
103
+ else
104
+ raise "not a symbol?: #{self.inspect}"
105
+ end
106
+ end
107
+ end
108
+
109
+ class NodeParser < ::Ripper # :nodoc:
110
+ PARSER_EVENTS.each do |event|
111
+ arity = PARSER_EVENT_TABLE[event]
112
+ if arity == 0 && event.to_s.end_with?("_new")
113
+ module_eval(<<-eof, __FILE__, __LINE__ + 1)
114
+ def on_#{event}(*args)
115
+ Node.new(:list, args, lineno: lineno(), column: column())
116
+ end
117
+ eof
118
+ elsif event.to_s.match?(/_add(_.+)?\z/)
119
+ module_eval(<<-eof, __FILE__, __LINE__ + 1)
120
+ begin; undef on_#{event}; rescue NameError; end
121
+ def on_#{event}(list, item)
122
+ list.push(item)
123
+ list
124
+ end
125
+ eof
126
+ else
127
+ module_eval(<<-eof, __FILE__, __LINE__ + 1)
128
+ begin; undef on_#{event}; rescue NameError; end
129
+ def on_#{event}(*args)
130
+ Node.new(:#{event}, args, lineno: lineno(), column: column())
131
+ end
132
+ eof
133
+ end
134
+ end
135
+
136
+ SCANNER_EVENTS.each do |event|
137
+ module_eval(<<-End, __FILE__, __LINE__ + 1)
138
+ def on_#{event}(tok)
139
+ Node.new(:@#{event}, [tok], lineno: lineno(), column: column())
140
+ end
141
+ End
142
+ end
143
+ end
144
+
145
+ class RenderCallExtractor < NodeParser # :nodoc:
146
+ attr_reader :render_calls
147
+
148
+ METHODS_TO_PARSE = %w(render render_to_string)
149
+
150
+ def initialize(*args)
151
+ super
152
+
153
+ @render_calls = []
154
+ end
155
+
156
+ private
157
+ def on_fcall(name, *args)
158
+ on_render_call(super)
159
+ end
160
+
161
+ def on_command(name, *args)
162
+ on_render_call(super)
163
+ end
164
+
165
+ def on_render_call(node)
166
+ METHODS_TO_PARSE.each do |method|
167
+ if node.fcall_named?(method)
168
+ @render_calls << [method, node]
169
+ return node
170
+ end
171
+ end
172
+ node
173
+ end
174
+
175
+ def on_arg_paren(content)
176
+ content
177
+ end
178
+
179
+ def on_paren(content)
180
+ content.size == 1 ? content.first : content
181
+ end
182
+ end
183
+
184
+ def render_calls
185
+ parser = RenderCallExtractor.new(@code)
186
+ parser.parse
187
+
188
+ parser.render_calls.group_by(&:first).to_h do |method, nodes|
189
+ [ method.to_sym, nodes.collect { |v| v[1] } ]
190
+ end.map do |method, nodes|
191
+ nodes.map { |n| parse_render(n) }
192
+ end.flatten.compact
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
339
+ end
340
+ end
341
+ end