189 lines
4.8 KiB
Ruby
189 lines
4.8 KiB
Ruby
# 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
|