eager_autoload do
autoload :Error
autoload :RawFile
autoload :Renderable
autoload :Handlers
autoload :HTML
autoload :Inline
autoload :Sources
autoload :Text
autoload :Types
end
extend Template::Handlers
attr_reader :identifier, :handler
attr_reader :variable, :format, :variant, :locals, :virtual_path
def initialize(source, identifier, handler, locals,, format: nil, variant: nil, virtual_path: nil)
@source = source
@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
end
def supports_streaming?
handler.respond_to?(:supports_streaming?) && handler.supports_streaming?
end
def render(view, locals, buffer = ActionView::OutputBuffer.new, add_to_stack: true, &block)
instrument_render_template do
compile!(view)
view._run(method_name, self, locals, buffer, add_to_stack: add_to_stack, &block)
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
def encode!
source = self.source
return source unless source.encoding == Encoding::BINARY
if source.sub!(/\A#{ENCODING_FLAG}/, "")
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 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
private
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 compile(mod)
source = encode!
code = @handler.call(self, source)
original_source = source
source = + def #{method_name}(local_assigns, output_buffer) @virtual_path =
source.force_encoding(code.encoding)
source.encode!
unless source.valid_encoding?
raise WrongEncodingError.new(source, Encoding.default_internal)
end
begin
mod.module_eval(source, identifier, 0)
rescue SyntaxError
raise SyntaxErrorInTemplate.new(self, original_source)
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
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 method_name
@method_name ||= begin
m = +"_#{identifier_method_name}__#{@identifier.hash}_#{__id__}"
m.tr!("-", "_")
m
end
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