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,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
|