/
render_parser.rb
188 lines (147 loc) · 4.83 KB
/
render_parser.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# frozen_string_literal: true
require "action_view/ripper_ast_parser"
module ActionView
class RenderParser # :nodoc:
def initialize(name, code)
@name = name
@code = code
@parser = RipperASTParser
end
def render_calls
render_nodes = @parser.parse_render_nodes(@code)
render_nodes.map do |method, nodes|
nodes.map { |n| send(:parse_render, n) }
end.flatten.compact
end
private
def directory
File.dirname(@name)
end
def resolve_path_directory(path)
if path.include?("/")
path
else
"#{directory}/#{path}"
end
end
# Convert
# render("foo", ...)
# into either
# render(template: "foo", ...)
# or
# render(partial: "foo", ...)
def normalize_args(string, options_hash)
if options_hash
{ partial: string, locals: options_hash }
else
{ partial: string }
end
end
def parse_render(node)
node = node.argument_nodes
if (node.length == 1 || node.length == 2) && !node[0].hash?
if node.length == 1
options = normalize_args(node[0], nil)
elsif node.length == 2
options = normalize_args(node[0], node[1])
end
return nil unless options
parse_render_from_options(options)
elsif node.length == 1 && node[0].hash?
options = parse_hash_to_symbols(node[0])
return nil unless options
parse_render_from_options(options)
else
nil
end
end
def parse_hash(node)
node.hash? && node.to_hash
end
def parse_hash_to_symbols(node)
hash = parse_hash(node)
return unless hash
hash.transform_keys do |key_node|
key = parse_sym(key_node)
return unless key
key
end
end
ALL_KNOWN_KEYS = [:partial, :template, :layout, :formats, :locals, :object, :collection, :as, :status, :content_type, :location, :spacer_template]
RENDER_TYPE_KEYS =
[:partial, :template, :layout]
def parse_render_from_options(options_hash)
renders = []
keys = options_hash.keys
if (keys & RENDER_TYPE_KEYS).size < 1
# Must have at least one of render keys
return nil
end
if (keys - ALL_KNOWN_KEYS).any?
# de-opt in case of unknown option
return nil
end
render_type = (keys & RENDER_TYPE_KEYS)[0]
node = options_hash[render_type]
if node.string?
template = resolve_path_directory(node.to_string)
else
if node.variable_reference?
dependency = node.variable_name.sub(/\A(?:\$|@{1,2})/, "")
elsif node.vcall?
dependency = node.variable_name
elsif node.call?
dependency = node.call_method_name
else
return
end
object_template = true
template = "#{dependency.pluralize}/#{dependency.singularize}"
end
return unless template
if spacer_template = render_template_with_spacer?(options_hash)
virtual_path = partial_to_virtual_path(:partial, spacer_template)
renders << virtual_path
end
if options_hash.key?(:object) || options_hash.key?(:collection) || object_template
return nil if options_hash.key?(:object) && options_hash.key?(:collection)
return nil unless options_hash.key?(:partial)
end
virtual_path = partial_to_virtual_path(render_type, template)
renders << virtual_path
# Support for rendering multiple templates (i.e. a partial with a layout)
if layout_template = render_template_with_layout?(render_type, options_hash)
virtual_path = partial_to_virtual_path(:layout, layout_template)
renders << virtual_path
end
renders
end
def parse_str(node)
node.string? && node.to_string
end
def parse_sym(node)
node.symbol? && node.to_symbol
end
private
def render_template_with_layout?(render_type, options_hash)
if render_type != :layout && options_hash.key?(:layout)
parse_str(options_hash[:layout])
end
end
def render_template_with_spacer?(options_hash)
if options_hash.key?(:spacer_template)
parse_str(options_hash[:spacer_template])
end
end
def partial_to_virtual_path(render_type, partial_path)
if render_type == :partial || render_type == :layout
partial_path.gsub(%r{(/|^)([^/]*)\z}, '\1_\2')
else
partial_path
end
end
def layout_to_virtual_path(layout_path)
"layouts/#{layout_path}"
end
end
end