eager_autoload do
autoload :Error
autoload :RawFile
autoload :Renderable
autoload :Handlers
autoload :HTML
autoload :Inline
autoload :Types
autoload :Sources
autoload :Text
autoload :Types
end
extend Template::Handlers
singleton_class.attr_accessor :frozen_string_literal
@frozen_string_literal = false
class << self
def mime_types_implementation=(implementation)
if self::Types != implementation
remove_const(:Types)
const_set(:Types, implementation)
end
end
end
attr_reader :identifier, :handler
attr_reader :variable, :format, :variant, :virtual_path
NONE = Object.new
def initialize(source, identifier, handler, locals,, format: nil, variant: nil, virtual_path: nil)
@source = source.dup
@identifier = identifier
@handler = handler
@compiled = false
@locals = locals
@virtual_path = virtual_path
@variable = if @virtual_path
base = @virtual_path.end_with?("/") ? "" : ::File.basename(@virtual_path)
base =~ /\A_?(.*?)(?:\.\w+)*\z/
$1.to_sym
end
@format = format
@variant = variant
@compile_mutex = Mutex.new
@strict_locals = NONE
@strict_local_keys = nil
@type = nil
end
def locals
if strict_locals?
nil
else
@locals
end
end
def spot(location)
ast = RubyVM::AbstractSyntaxTree.parse(compiled_source, keep_script_lines: true)
node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(location)
node = find_node_by_id(ast, node_id)
ErrorHighlight.spot(node)
end
def translate_location(backtrace_location, spot)
if handler.respond_to?(:translate_location)
handler.translate_location(spot, backtrace_location, encode!) || spot
else
spot
end
end
def supports_streaming?
handler.respond_to?(:supports_streaming?) && handler.supports_streaming?
end
def render(view, locals, buffer = nil, implicit_locals: [], add_to_stack: true, &block)
instrument_render_template do
compile!(view)
if strict_locals? && @strict_local_keys && !implicit_locals.empty?
locals_to_ignore = implicit_locals - @strict_local_keys
locals.except!(*locals_to_ignore)
end
if buffer
view._run(method_name, self, locals, buffer, add_to_stack: add_to_stack, has_strict_locals: strict_locals?, &block)
nil
else
view._run(method_name, self, locals, OutputBuffer.new, add_to_stack: add_to_stack, has_strict_locals: strict_locals?, &block)&.to_s
end
end
rescue => e
handle_render_error(view, e)
end
def type
@type ||= Types[format]
end
def short_identifier
@short_identifier ||= defined?(Rails.root) ? identifier.delete_prefix("#{Rails.root}/") : identifier
end
def inspect
"#<#{self.class.name} #{short_identifier} locals=#{locals.inspect}>"
end
def source
@source.to_s
end
LEADING_ENCODING_REGEXP = /\A#{ENCODING_FLAG}/
private_constant :LEADING_ENCODING_REGEXP
def encode!
source = self.source
return source unless source.encoding == Encoding::BINARY
if source.sub!(LEADING_ENCODING_REGEXP, "")
encoding = magic_encoding = $1
else
encoding = Encoding.default_external
end
source.force_encoding(encoding)
if !magic_encoding && @handler.respond_to?(:handles_encoding?) && @handler.handles_encoding?
source
elsif source.valid_encoding?
source.encode!
else
raise WrongEncodingError.new(source, encoding)
end
end
def strict_locals!
if @strict_locals == NONE
self.source.sub!(STRICT_LOCALS_REGEX, "")
@strict_locals = $1
return if @strict_locals.nil?
@strict_locals = "**nil" if @strict_locals.blank?
end
@strict_locals
end
def strict_locals?
strict_locals!
end
def marshal_dump
[ @source, @identifier, @handler, @compiled, @locals, @virtual_path, @format, @variant ]
end
def marshal_load(array)
@source, @identifier, @handler, @compiled, @locals, @virtual_path, @format, @variant = *array
@compile_mutex = Mutex.new
end
def method_name
@method_name ||= begin
m = +"_#{identifier_method_name}__#{@identifier.hash}_#{__id__}"
m.tr!("-", "_")
m
end
end
private
def find_node_by_id(node, node_id)
return node if node.node_id == node_id
node.children.grep(node.class).each do |child|
found = find_node_by_id(child, node_id)
return found if found
end
false
end
def compile!(view)
return if @compiled
@compile_mutex.synchronize do
return if @compiled
mod = view.compiled_method_container
instrument("!compile_template") do
compile(mod)
end
@compiled = true
end
end
def compiled_source
set_strict_locals = strict_locals!
source = encode!
code = @handler.call(self, source)
method_arguments =
if set_strict_locals
"output_buffer, #{set_strict_locals}"
else
"local_assigns, output_buffer"
end
source = + def #{method_name}(
source.force_encoding(code.encoding)
source.encode!
unless source.valid_encoding?
raise WrongEncodingError.new(source, Encoding.default_internal)
end
if Template.frozen_string_literal
"# frozen_string_literal: true\n#{source}"
else
source
end
end
def compile(mod)
begin
mod.module_eval(compiled_source, identifier, offset)
rescue SyntaxError
raise SyntaxErrorInTemplate.new(self, encode!)
end
return unless strict_locals?
parameters = mod.instance_method(method_name).parameters - [[:req, :output_buffer]]
non_kwarg_parameters = parameters.select do |parameter|
![:keyreq, :key, :keyrest, :nokey].include?(parameter[0])
end
unless non_kwarg_parameters.empty?
mod.undef_method(method_name)
raise ArgumentError.new(
"#{non_kwarg_parameters.map { |_, name| "`#{name}`" }.to_sentence} set as non-keyword " "#{'argument'.pluralize(non_kwarg_parameters.length)} for #{short_identifier}. " "Locals can only be set as keyword arguments."
)
end
unless parameters.any? { |type, _| type == :keyrest }
parameters.map!(&:last)
parameters.sort!
@strict_local_keys = parameters.freeze
end
end
def offset
if Template.frozen_string_literal
-1
else
0
end
end
def handle_render_error(view, e)
if e.is_a?(Template::Error)
e.sub_template_of(self)
raise e
else
raise Template::Error.new(self)
end
end
def locals_code
return "" if strict_locals?
locals = @locals - Module::RUBY_RESERVED_KEYWORDS
locals = locals.grep(/\A(?![A-Z0-9])(?:[[:alnum:]_]|[^\00--\1177])+\z/)
locals.each_with_object(+"") { |key, code| code << "#{key} = local_assigns[:#{key}]; #{key} = #{key};" }
end
def identifier_method_name
short_identifier.tr("^a-z_", "_")
end
def instrument(action, &block)
ActiveSupport::Notifications.instrument("#{action}.action_view", instrument_payload, &block)
end
def instrument_render_template(&block)
ActiveSupport::Notifications.instrument("!render_template.action_view", instrument_payload, &block)
end
def instrument_payload
{ virtual_path: @virtual_path, identifier: @identifier }
end
end