-
Notifications
You must be signed in to change notification settings - Fork 21.4k
/
erb.rb
157 lines (127 loc) · 5.21 KB
/
erb.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
# frozen_string_literal: true
require "strscan"
require "active_support/core_ext/erb/util"
module ActionView
class Template
module Handlers
class ERB
autoload :Erubi, "action_view/template/handlers/erb/erubi"
# Specify trim mode for the ERB compiler. Defaults to '-'.
# See ERB documentation for suitable values.
class_attribute :erb_trim_mode, default: "-"
# Default implementation used.
class_attribute :erb_implementation, default: Erubi
# Do not escape templates of these mime types.
class_attribute :escape_ignore_list, default: ["text/plain"]
# Strip trailing newlines from rendered output
class_attribute :strip_trailing_newlines, default: false
ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
LocationParsingError = Class.new(StandardError) # :nodoc:
def self.call(template, source)
new.call(template, source)
end
def supports_streaming?
true
end
def handles_encoding?
true
end
# Translate an error location returned by ErrorHighlight to the correct
# source location inside the template.
def translate_location(spot, backtrace_location, source)
# Tokenize the source line
tokens = ::ERB::Util.tokenize(source.lines[backtrace_location.lineno - 1])
new_first_column = find_offset(spot[:snippet], tokens, spot[:first_column])
lineno_delta = spot[:first_lineno] - backtrace_location.lineno
spot[:first_lineno] -= lineno_delta
spot[:last_lineno] -= lineno_delta
column_delta = spot[:first_column] - new_first_column
spot[:first_column] -= column_delta
spot[:last_column] -= column_delta
spot[:script_lines] = source.lines
spot
rescue NotImplementedError, LocationParsingError
nil
end
def call(template, source)
# First, convert to BINARY, so in case the encoding is
# wrong, we can still find an encoding tag
# (<%# encoding %>) inside the String using a regular
# expression
template_source = source.b
erb = template_source.gsub(ENCODING_TAG, "")
encoding = $2
erb.force_encoding valid_encoding(source.dup, encoding)
# Always make sure we return a String in the default_internal
erb.encode!
# Strip trailing newlines from the template if enabled
erb.chomp! if strip_trailing_newlines
options = {
escape: (self.class.escape_ignore_list.include? template.type),
trim: (self.class.erb_trim_mode == "-")
}
if ActionView::Base.annotate_rendered_view_with_filenames && template.format == :html
options[:preamble] = "@output_buffer.safe_append='<!-- BEGIN #{template.short_identifier} -->';"
options[:postamble] = "@output_buffer.safe_append='<!-- END #{template.short_identifier} -->';@output_buffer"
end
self.class.erb_implementation.new(erb, options).src
end
private
def valid_encoding(string, encoding)
# If a magic encoding comment was found, tag the
# String with this encoding. This is for a case
# where the original String was assumed to be,
# for instance, UTF-8, but a magic comment
# proved otherwise
string.force_encoding(encoding) if encoding
# If the String is valid, return the encoding we found
return string.encoding if string.valid_encoding?
# Otherwise, raise an exception
raise WrongEncodingError.new(string, string.encoding)
end
def find_offset(compiled, source_tokens, error_column)
compiled = StringScanner.new(compiled)
passed_tokens = []
while tok = source_tokens.shift
tok_name, str = *tok
case tok_name
when :TEXT
loop do
break if compiled.match?(str)
compiled.getch
end
raise LocationParsingError unless compiled.scan(str)
when :CODE
if compiled.pos > error_column
raise LocationParsingError, "We went too far"
end
if compiled.pos + str.bytesize >= error_column
offset = error_column - compiled.pos
return passed_tokens.map(&:last).join.bytesize + offset
else
unless compiled.scan(str)
raise LocationParsingError, "Couldn't find code snippet"
end
end
when :OPEN
next_tok = source_tokens.first.last
loop do
break if compiled.match?(next_tok)
compiled.getch
end
when :CLOSE
next_tok = source_tokens.first.last
loop do
break if compiled.match?(next_tok)
compiled.getch
end
else
raise LocationParsingError, "Not implemented: #{tok.first}"
end
passed_tokens << tok
end
end
end
end
end
end