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
|
# encoding: utf-8
# This is so that templates compiled in this file are UTF-8
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/object/blank'
module ActionView
class Template
extend ActiveSupport::Autoload
eager_autoload do
autoload :Error
autoload :Handler
autoload :Handlers
autoload :Text
end
extend Template::Handlers
attr_reader :source, :identifier, :handler, :virtual_path, :formats
Finalizer = proc do |method_name|
proc do
ActionView::CompiledTemplates.module_eval do
remove_possible_method method_name
end
end
end
def initialize(source, identifier, handler, details)
if source.encoding_aware? && source =~ %r{\A#{ENCODING_FLAG}}
# don't snip off the \n to preserve line numbers
source.sub!(/\A[^\n]*/, '')
source.force_encoding($1).encode
end
@source = source
@identifier = identifier
@handler = handler
@virtual_path = details[:virtual_path]
@method_names = {}
format = details[:format] || :html
@formats = Array.wrap(format).map(&:to_sym)
end
def render(view, locals, &block)
# Notice that we use a bang in this instrumentation because you don't want to
# consume this in production. This is only slow if it's being listened to.
ActiveSupport::Notifications.instrument("!render_template.action_view", :virtual_path => @virtual_path) do
method_name = compile(locals, view)
view.send(method_name, locals, &block)
end
rescue Exception => e
if e.is_a?(Template::Error)
e.sub_template_of(self)
raise e
else
raise Template::Error.new(self, view.assigns, e)
end
end
def mime_type
@mime_type ||= Mime::Type.lookup_by_extension(@formats.first.to_s) if @formats.first
end
def variable_name
@variable_name ||= @virtual_path[%r'_?(\w+)(\.\w+)*$', 1].to_sym
end
def counter_name
@counter_name ||= "#{variable_name}_counter".to_sym
end
def inspect
if defined?(Rails.root)
identifier.sub("#{Rails.root}/", '')
else
identifier
end
end
private
def compile(locals, view)
method_name = build_method_name(locals)
return method_name if view.respond_to?(method_name)
locals_code = locals.keys.map! { |key| "#{key} = local_assigns[:#{key}];" }.join
code = @handler.call(self)
if code.sub!(/\A(#.*coding.*)\n/, '')
encoding_comment = $1
elsif defined?(Encoding) && Encoding.respond_to?(:default_external)
encoding_comment = "#coding:#{Encoding.default_external}"
end
source = <<-end_src
def #{method_name}(local_assigns)
_old_virtual_path, @_virtual_path = @_virtual_path, #{@virtual_path.inspect};_old_output_buffer = output_buffer;#{locals_code};#{code}
ensure
@_virtual_path, self.output_buffer = _old_virtual_path, _old_output_buffer
end
end_src
if encoding_comment
source = "#{encoding_comment}\n#{source}"
line = -1
else
line = 0
end
begin
ActionView::CompiledTemplates.module_eval(source, identifier, line)
ObjectSpace.define_finalizer(self, Finalizer[method_name])
method_name
rescue Exception => e # errors from template code
if logger = (view && view.logger)
logger.debug "ERROR: compiling #{method_name} RAISED #{e}"
logger.debug "Function body: #{source}"
logger.debug "Backtrace: #{e.backtrace.join("\n")}"
end
raise ActionView::Template::Error.new(self, {}, e)
end
end
def build_method_name(locals)
# TODO: is locals.keys.hash reliably the same?
@method_names[locals.keys.hash] ||=
"_render_template_#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_")
end
end
end
|