omg-actionview 8.0.0.alpha1

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 (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,139 @@
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
+ case node.type
101
+ when :string_node
102
+ path = node.unescaped
103
+ path.include?("/") ? path : "#{directory}/#{path}"
104
+ when :interpolated_string_node
105
+ node.parts.map do |node|
106
+ case node.type
107
+ when :string_node
108
+ node.unescaped
109
+ when :embedded_statements_node
110
+ "*"
111
+ else
112
+ return
113
+ end
114
+ end.join("")
115
+ else
116
+ dependency =
117
+ case node.type
118
+ when :class_variable_read_node
119
+ node.slice[2..]
120
+ when :instance_variable_read_node
121
+ node.slice[1..]
122
+ when :global_variable_read_node
123
+ node.slice[1..]
124
+ when :local_variable_read_node
125
+ node.slice
126
+ when :call_node
127
+ node.name.to_s
128
+ else
129
+ return
130
+ end
131
+
132
+ "#{dependency.pluralize}/#{dependency.singularize}"
133
+ end
134
+
135
+ [template, object_template]
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,350 @@
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
+
70
+ # s(:string_literal, s(:string_content, map))
71
+ self[0].map do |node|
72
+ case node.type
73
+ when :@tstring_content
74
+ node[0]
75
+ when :string_embexpr
76
+ "*"
77
+ end
78
+ end.join("")
79
+ end
80
+
81
+ def hash?
82
+ type == :bare_assoc_hash || type == :hash
83
+ end
84
+
85
+ def to_hash
86
+ if type == :bare_assoc_hash
87
+ hash_from_body(self[0])
88
+ elsif type == :hash && self[0] == nil
89
+ {}
90
+ elsif type == :hash && self[0].type == :assoclist_from_args
91
+ hash_from_body(self[0][0])
92
+ end
93
+ end
94
+
95
+ def hash_from_body(body)
96
+ body.to_h do |hash_node|
97
+ return nil if hash_node.type != :assoc_new
98
+
99
+ [hash_node[0], hash_node[1]]
100
+ end
101
+ end
102
+
103
+ def symbol?
104
+ type == :@label || type == :symbol_literal
105
+ end
106
+
107
+ def to_symbol
108
+ if type == :@label && self[0] =~ /\A(.+):\z/
109
+ $1.to_sym
110
+ elsif type == :symbol_literal && self[0].type == :symbol && self[0][0].type == :@ident
111
+ self[0][0][0].to_sym
112
+ else
113
+ raise "not a symbol?: #{self.inspect}"
114
+ end
115
+ end
116
+ end
117
+
118
+ class NodeParser < ::Ripper # :nodoc:
119
+ PARSER_EVENTS.each do |event|
120
+ arity = PARSER_EVENT_TABLE[event]
121
+ if arity == 0 && event.to_s.end_with?("_new")
122
+ module_eval(<<-eof, __FILE__, __LINE__ + 1)
123
+ def on_#{event}(*args)
124
+ Node.new(:list, args, lineno: lineno(), column: column())
125
+ end
126
+ eof
127
+ elsif event.to_s.match?(/_add(_.+)?\z/)
128
+ module_eval(<<-eof, __FILE__, __LINE__ + 1)
129
+ begin; undef on_#{event}; rescue NameError; end
130
+ def on_#{event}(list, item)
131
+ list.push(item)
132
+ list
133
+ end
134
+ eof
135
+ else
136
+ module_eval(<<-eof, __FILE__, __LINE__ + 1)
137
+ begin; undef on_#{event}; rescue NameError; end
138
+ def on_#{event}(*args)
139
+ Node.new(:#{event}, args, lineno: lineno(), column: column())
140
+ end
141
+ eof
142
+ end
143
+ end
144
+
145
+ SCANNER_EVENTS.each do |event|
146
+ module_eval(<<-End, __FILE__, __LINE__ + 1)
147
+ def on_#{event}(tok)
148
+ Node.new(:@#{event}, [tok], lineno: lineno(), column: column())
149
+ end
150
+ End
151
+ end
152
+ end
153
+
154
+ class RenderCallExtractor < NodeParser # :nodoc:
155
+ attr_reader :render_calls
156
+
157
+ METHODS_TO_PARSE = %w(render render_to_string)
158
+
159
+ def initialize(*args)
160
+ super
161
+
162
+ @render_calls = []
163
+ end
164
+
165
+ private
166
+ def on_fcall(name, *args)
167
+ on_render_call(super)
168
+ end
169
+
170
+ def on_command(name, *args)
171
+ on_render_call(super)
172
+ end
173
+
174
+ def on_render_call(node)
175
+ METHODS_TO_PARSE.each do |method|
176
+ if node.fcall_named?(method)
177
+ @render_calls << [method, node]
178
+ return node
179
+ end
180
+ end
181
+ node
182
+ end
183
+
184
+ def on_arg_paren(content)
185
+ content
186
+ end
187
+
188
+ def on_paren(content)
189
+ content.size == 1 ? content.first : content
190
+ end
191
+ end
192
+
193
+ def render_calls
194
+ parser = RenderCallExtractor.new(@code)
195
+ parser.parse
196
+
197
+ parser.render_calls.group_by(&:first).to_h do |method, nodes|
198
+ [ method.to_sym, nodes.collect { |v| v[1] } ]
199
+ end.map do |method, nodes|
200
+ nodes.map { |n| parse_render(n) }
201
+ end.flatten.compact
202
+ end
203
+
204
+ private
205
+ def resolve_path_directory(path)
206
+ if path.include?("/")
207
+ path
208
+ else
209
+ "#{directory}/#{path}"
210
+ end
211
+ end
212
+
213
+ # Convert
214
+ # render("foo", ...)
215
+ # into either
216
+ # render(template: "foo", ...)
217
+ # or
218
+ # render(partial: "foo", ...)
219
+ def normalize_args(string, options_hash)
220
+ if options_hash
221
+ { partial: string, locals: options_hash }
222
+ else
223
+ { partial: string }
224
+ end
225
+ end
226
+
227
+ def parse_render(node)
228
+ node = node.argument_nodes
229
+
230
+ if (node.length == 1 || node.length == 2) && !node[0].hash?
231
+ if node.length == 1
232
+ options = normalize_args(node[0], nil)
233
+ elsif node.length == 2
234
+ options = normalize_args(node[0], node[1])
235
+ end
236
+
237
+ return nil unless options
238
+
239
+ parse_render_from_options(options)
240
+ elsif node.length == 1 && node[0].hash?
241
+ options = parse_hash_to_symbols(node[0])
242
+
243
+ return nil unless options
244
+
245
+ parse_render_from_options(options)
246
+ else
247
+ nil
248
+ end
249
+ end
250
+
251
+ def parse_hash(node)
252
+ node.hash? && node.to_hash
253
+ end
254
+
255
+ def parse_hash_to_symbols(node)
256
+ hash = parse_hash(node)
257
+
258
+ return unless hash
259
+
260
+ hash.transform_keys do |key_node|
261
+ key = parse_sym(key_node)
262
+
263
+ return unless key
264
+
265
+ key
266
+ end
267
+ end
268
+
269
+ def parse_render_from_options(options_hash)
270
+ renders = []
271
+ keys = options_hash.keys
272
+
273
+ if (keys & RENDER_TYPE_KEYS).size < 1
274
+ # Must have at least one of render keys
275
+ return nil
276
+ end
277
+
278
+ if (keys - ALL_KNOWN_KEYS).any?
279
+ # de-opt in case of unknown option
280
+ return nil
281
+ end
282
+
283
+ render_type = (keys & RENDER_TYPE_KEYS)[0]
284
+
285
+ node = options_hash[render_type]
286
+
287
+ if node.string?
288
+ template = resolve_path_directory(node.to_string)
289
+ else
290
+ if node.variable_reference?
291
+ dependency = node.variable_name.sub(/\A(?:\$|@{1,2})/, "")
292
+ elsif node.vcall?
293
+ dependency = node.variable_name
294
+ elsif node.call?
295
+ dependency = node.call_method_name
296
+ else
297
+ return
298
+ end
299
+
300
+ object_template = true
301
+ template = "#{dependency.pluralize}/#{dependency.singularize}"
302
+ end
303
+
304
+ return unless template
305
+
306
+ if spacer_template = render_template_with_spacer?(options_hash)
307
+ virtual_path = partial_to_virtual_path(:partial, spacer_template)
308
+ renders << virtual_path
309
+ end
310
+
311
+ if options_hash.key?(:object) || options_hash.key?(:collection) || object_template
312
+ return nil if options_hash.key?(:object) && options_hash.key?(:collection)
313
+ return nil unless options_hash.key?(:partial)
314
+ end
315
+
316
+ virtual_path = partial_to_virtual_path(render_type, template)
317
+ renders << virtual_path
318
+
319
+ # Support for rendering multiple templates (i.e. a partial with a layout)
320
+ if layout_template = render_template_with_layout?(render_type, options_hash)
321
+ virtual_path = partial_to_virtual_path(:layout, layout_template)
322
+
323
+ renders << virtual_path
324
+ end
325
+
326
+ renders
327
+ end
328
+
329
+ def parse_str(node)
330
+ node.string? && node.to_string
331
+ end
332
+
333
+ def parse_sym(node)
334
+ node.symbol? && node.to_symbol
335
+ end
336
+
337
+ def render_template_with_layout?(render_type, options_hash)
338
+ if render_type != :layout && options_hash.key?(:layout)
339
+ parse_str(options_hash[:layout])
340
+ end
341
+ end
342
+
343
+ def render_template_with_spacer?(options_hash)
344
+ if options_hash.key?(:spacer_template)
345
+ parse_str(options_hash[:spacer_template])
346
+ end
347
+ end
348
+ end
349
+ end
350
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module RenderParser # :nodoc:
5
+ ALL_KNOWN_KEYS = [:partial, :template, :layout, :formats, :locals, :object, :collection, :as, :status, :content_type, :location, :spacer_template]
6
+ RENDER_TYPE_KEYS = [:partial, :template, :layout]
7
+
8
+ class Base # :nodoc:
9
+ def initialize(name, code)
10
+ @name = name
11
+ @code = code
12
+ end
13
+
14
+ private
15
+ def directory
16
+ File.dirname(@name)
17
+ end
18
+
19
+ def partial_to_virtual_path(render_type, partial_path)
20
+ if render_type == :partial || render_type == :layout
21
+ partial_path.gsub(%r{(/|^)([^/]*)\z}, '\1_\2')
22
+ else
23
+ partial_path
24
+ end
25
+ end
26
+ end
27
+
28
+ # Check if prism is available. If it is, use it. Otherwise, use ripper.
29
+ begin
30
+ require "prism"
31
+ rescue LoadError
32
+ require "ripper"
33
+ require_relative "render_parser/ripper_render_parser"
34
+ Default = RipperRenderParser
35
+ else
36
+ require_relative "render_parser/prism_render_parser"
37
+ Default = PrismRenderParser
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent/map"
4
+
5
+ module ActionView
6
+ # This class defines the interface for a renderer. Each class that
7
+ # subclasses +AbstractRenderer+ is used by the base +Renderer+ class to
8
+ # render a specific type of object.
9
+ #
10
+ # The base +Renderer+ class uses its +render+ method to delegate to the
11
+ # renderers. These currently consist of
12
+ #
13
+ # PartialRenderer - Used for rendering partials
14
+ # TemplateRenderer - Used for rendering other types of templates
15
+ # StreamingTemplateRenderer - Used for streaming
16
+ #
17
+ # Whenever the +render+ method is called on the base +Renderer+ class, a new
18
+ # renderer object of the correct type is created, and the +render+ method on
19
+ # that new object is called in turn. This abstracts the set up and rendering
20
+ # into a separate classes for partials and templates.
21
+ class AbstractRenderer # :nodoc:
22
+ delegate :template_exists?, :any_templates?, :formats, to: :@lookup_context
23
+
24
+ def initialize(lookup_context)
25
+ @lookup_context = lookup_context
26
+ end
27
+
28
+ def render
29
+ raise NotImplementedError
30
+ end
31
+
32
+ module ObjectRendering # :nodoc:
33
+ PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k|
34
+ h.compute_if_absent(k) { Concurrent::Map.new }
35
+ end
36
+
37
+ def initialize(lookup_context, options)
38
+ super
39
+ @context_prefix = lookup_context.prefixes.first
40
+ end
41
+
42
+ private
43
+ def local_variable(path)
44
+ if as = @options[:as]
45
+ raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s)
46
+ as.to_sym
47
+ else
48
+ base = path.end_with?("/") ? "" : File.basename(path)
49
+ raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/
50
+ $1.to_sym
51
+ end
52
+ end
53
+
54
+ IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \
55
+ "make sure your partial name starts with underscore."
56
+
57
+ OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " \
58
+ "make sure it starts with lowercase letter, " \
59
+ "and is followed by any combination of letters, numbers and underscores."
60
+
61
+ def raise_invalid_identifier(path)
62
+ raise ArgumentError, IDENTIFIER_ERROR_MESSAGE % path
63
+ end
64
+
65
+ def raise_invalid_option_as(as)
66
+ raise ArgumentError, OPTION_AS_ERROR_MESSAGE % as
67
+ end
68
+
69
+ # Obtains the path to where the object's partial is located. If the object
70
+ # responds to +to_partial_path+, then +to_partial_path+ will be called and
71
+ # will provide the path. If the object does not respond to +to_partial_path+,
72
+ # then an +ArgumentError+ is raised.
73
+ #
74
+ # If +prefix_partial_path_with_controller_namespace+ is true, then this
75
+ # method will prefix the partial paths with a namespace.
76
+ def partial_path(object, view)
77
+ object = object.to_model if object.respond_to?(:to_model)
78
+
79
+ path = if object.respond_to?(:to_partial_path)
80
+ object.to_partial_path
81
+ else
82
+ raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement #to_partial_path.")
83
+ end
84
+
85
+ if view.prefix_partial_path_with_controller_namespace
86
+ PREFIXED_PARTIAL_NAMES[@context_prefix][path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
87
+ else
88
+ path
89
+ end
90
+ end
91
+
92
+ def merge_prefix_into_object_path(prefix, object_path)
93
+ if prefix.include?(?/) && object_path.include?(?/)
94
+ prefixes = []
95
+ prefix_array = File.dirname(prefix).split("/")
96
+ object_path_array = object_path.split("/")[0..-3] # skip model dir & partial
97
+
98
+ prefix_array.each_with_index do |dir, index|
99
+ break if dir == object_path_array[index]
100
+ prefixes << dir
101
+ end
102
+
103
+ (prefixes << object_path).join("/")
104
+ else
105
+ object_path
106
+ end
107
+ end
108
+ end
109
+
110
+ class RenderedCollection # :nodoc:
111
+ def self.empty(format)
112
+ EmptyCollection.new format
113
+ end
114
+
115
+ attr_reader :rendered_templates
116
+
117
+ def initialize(rendered_templates, spacer)
118
+ @rendered_templates = rendered_templates
119
+ @spacer = spacer
120
+ end
121
+
122
+ def body
123
+ @rendered_templates.map(&:body).join(@spacer.body).html_safe
124
+ end
125
+
126
+ def format
127
+ rendered_templates.first.format
128
+ end
129
+
130
+ class EmptyCollection
131
+ attr_reader :format
132
+
133
+ def initialize(format)
134
+ @format = format
135
+ end
136
+
137
+ def body; nil; end
138
+ end
139
+ end
140
+
141
+ class RenderedTemplate # :nodoc:
142
+ attr_reader :body, :template
143
+
144
+ def initialize(body, template)
145
+ @body = body
146
+ @template = template
147
+ end
148
+
149
+ def format
150
+ template.format
151
+ end
152
+
153
+ EMPTY_SPACER = Struct.new(:body).new
154
+ end
155
+
156
+ private
157
+ NO_DETAILS = {}.freeze
158
+
159
+ def extract_details(options) # :doc:
160
+ details = nil
161
+ LookupContext.registered_details.each do |key|
162
+ value = options[key]
163
+
164
+ if value
165
+ (details ||= {})[key] = Array(value)
166
+ end
167
+ end
168
+ details || NO_DETAILS
169
+ end
170
+
171
+ def prepend_formats(formats) # :doc:
172
+ formats = Array(formats)
173
+ return if formats.empty? || @lookup_context.html_fallback_for_js
174
+
175
+ @lookup_context.formats = formats | @lookup_context.formats
176
+ end
177
+
178
+ def build_rendered_template(content, template)
179
+ RenderedTemplate.new content, template
180
+ end
181
+
182
+ def build_rendered_collection(templates, spacer)
183
+ RenderedCollection.new templates, spacer
184
+ end
185
+ end
186
+ end