ed-precompiled_erb 5.0.3-x64-mingw-ucrt
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.document +6 -0
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/dependabot_automerge.yml +30 -0
- data/.github/workflows/sync-ruby.yml +33 -0
- data/.github/workflows/test.yml +36 -0
- data/.gitignore +12 -0
- data/.rdoc_options +1 -0
- data/BDSL +22 -0
- data/COPYING +56 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +2 -0
- data/NEWS.md +69 -0
- data/README.md +98 -0
- data/Rakefile +19 -0
- data/_doc/cgi.rb +3 -0
- data/_doc/erb_executable.md +240 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/erb.gemspec +36 -0
- data/ext/erb/escape/escape.c +102 -0
- data/ext/erb/escape/extconf.rb +9 -0
- data/lib/erb/3.2/escape.so +0 -0
- data/lib/erb/3.3/escape.so +0 -0
- data/lib/erb/3.4/escape.so +0 -0
- data/lib/erb/compiler.rb +488 -0
- data/lib/erb/def_method.rb +47 -0
- data/lib/erb/escape.so +0 -0
- data/lib/erb/util.rb +77 -0
- data/lib/erb/version.rb +5 -0
- data/lib/erb.rb +1220 -0
- data/libexec/erb +184 -0
- metadata +80 -0
data/bin/setup
ADDED
data/erb.gemspec
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require_relative 'lib/erb/version'
|
|
3
|
+
rescue LoadError
|
|
4
|
+
# for Ruby core repository
|
|
5
|
+
require_relative 'version'
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
Gem::Specification.new do |spec|
|
|
9
|
+
spec.name = 'ed-precompiled_erb'
|
|
10
|
+
spec.version = ERB.const_get(:VERSION, false)
|
|
11
|
+
spec.authors = ['Masatoshi SEKI', 'Takashi Kokubun']
|
|
12
|
+
spec.email = ['seki@ruby-lang.org', 'k0kubun@ruby-lang.org']
|
|
13
|
+
|
|
14
|
+
spec.summary = %q{An easy to use but powerful templating system for Ruby.}
|
|
15
|
+
spec.description = %q{An easy to use but powerful templating system for Ruby.}
|
|
16
|
+
spec.homepage = 'https://github.com/ruby/erb'
|
|
17
|
+
spec.licenses = ['Ruby', 'BSD-2-Clause']
|
|
18
|
+
|
|
19
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
20
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
|
21
|
+
|
|
22
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
|
23
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
24
|
+
end
|
|
25
|
+
spec.bindir = 'libexec'
|
|
26
|
+
spec.executables = ['erb']
|
|
27
|
+
spec.require_paths = ['lib']
|
|
28
|
+
|
|
29
|
+
spec.required_ruby_version = '>= 3.2.0'
|
|
30
|
+
|
|
31
|
+
if RUBY_ENGINE == 'jruby'
|
|
32
|
+
spec.platform = 'java'
|
|
33
|
+
else
|
|
34
|
+
spec.extensions = ['ext/erb/escape/extconf.rb']
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#include "ruby.h"
|
|
2
|
+
#include "ruby/encoding.h"
|
|
3
|
+
|
|
4
|
+
static VALUE rb_cERB, rb_mEscape, rb_cCGI;
|
|
5
|
+
static ID id_escapeHTML;
|
|
6
|
+
|
|
7
|
+
#define HTML_ESCAPE_MAX_LEN 6
|
|
8
|
+
|
|
9
|
+
static const struct {
|
|
10
|
+
uint8_t len;
|
|
11
|
+
char str[HTML_ESCAPE_MAX_LEN+1];
|
|
12
|
+
} html_escape_table[UCHAR_MAX+1] = {
|
|
13
|
+
#define HTML_ESCAPE(c, str) [c] = {rb_strlen_lit(str), str}
|
|
14
|
+
HTML_ESCAPE('\'', "'"),
|
|
15
|
+
HTML_ESCAPE('&', "&"),
|
|
16
|
+
HTML_ESCAPE('"', """),
|
|
17
|
+
HTML_ESCAPE('<', "<"),
|
|
18
|
+
HTML_ESCAPE('>', ">"),
|
|
19
|
+
#undef HTML_ESCAPE
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
static inline void
|
|
23
|
+
preserve_original_state(VALUE orig, VALUE dest)
|
|
24
|
+
{
|
|
25
|
+
rb_enc_associate(dest, rb_enc_get(orig));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static inline long
|
|
29
|
+
escaped_length(VALUE str)
|
|
30
|
+
{
|
|
31
|
+
const long len = RSTRING_LEN(str);
|
|
32
|
+
if (len >= LONG_MAX / HTML_ESCAPE_MAX_LEN) {
|
|
33
|
+
ruby_malloc_size_overflow(len, HTML_ESCAPE_MAX_LEN);
|
|
34
|
+
}
|
|
35
|
+
return len * HTML_ESCAPE_MAX_LEN;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static VALUE
|
|
39
|
+
optimized_escape_html(VALUE str)
|
|
40
|
+
{
|
|
41
|
+
VALUE vbuf;
|
|
42
|
+
char *buf = ALLOCV_N(char, vbuf, escaped_length(str));
|
|
43
|
+
const char *cstr = RSTRING_PTR(str);
|
|
44
|
+
const char *end = cstr + RSTRING_LEN(str);
|
|
45
|
+
|
|
46
|
+
char *dest = buf;
|
|
47
|
+
while (cstr < end) {
|
|
48
|
+
const unsigned char c = *cstr++;
|
|
49
|
+
uint8_t len = html_escape_table[c].len;
|
|
50
|
+
if (len) {
|
|
51
|
+
memcpy(dest, html_escape_table[c].str, len);
|
|
52
|
+
dest += len;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
*dest++ = c;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
VALUE escaped = str;
|
|
60
|
+
if (RSTRING_LEN(str) < (dest - buf)) {
|
|
61
|
+
escaped = rb_str_new(buf, dest - buf);
|
|
62
|
+
preserve_original_state(str, escaped);
|
|
63
|
+
}
|
|
64
|
+
ALLOCV_END(vbuf);
|
|
65
|
+
return escaped;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/*
|
|
69
|
+
* ERB::Util.html_escape is similar to CGI.escapeHTML but different in the following two parts:
|
|
70
|
+
*
|
|
71
|
+
* * ERB::Util.html_escape converts an argument with #to_s first (only if it's not T_STRING)
|
|
72
|
+
* * ERB::Util.html_escape does not allocate a new string when nothing needs to be escaped
|
|
73
|
+
*/
|
|
74
|
+
static VALUE
|
|
75
|
+
erb_escape_html(VALUE self, VALUE str)
|
|
76
|
+
{
|
|
77
|
+
if (!RB_TYPE_P(str, T_STRING)) {
|
|
78
|
+
str = rb_convert_type(str, T_STRING, "String", "to_s");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (rb_enc_str_asciicompat_p(str)) {
|
|
82
|
+
return optimized_escape_html(str);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
return rb_funcall(rb_cCGI, id_escapeHTML, 1, str);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
void
|
|
90
|
+
Init_escape(void)
|
|
91
|
+
{
|
|
92
|
+
#ifdef HAVE_RB_EXT_RACTOR_SAFE
|
|
93
|
+
rb_ext_ractor_safe(true);
|
|
94
|
+
#endif
|
|
95
|
+
|
|
96
|
+
rb_cERB = rb_define_class("ERB", rb_cObject);
|
|
97
|
+
rb_mEscape = rb_define_module_under(rb_cERB, "Escape");
|
|
98
|
+
rb_define_module_function(rb_mEscape, "html_escape", erb_escape_html, 1);
|
|
99
|
+
|
|
100
|
+
rb_cCGI = rb_define_class("CGI", rb_cObject);
|
|
101
|
+
id_escapeHTML = rb_intern("escapeHTML");
|
|
102
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/erb/compiler.rb
ADDED
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#--
|
|
3
|
+
# ERB::Compiler
|
|
4
|
+
#
|
|
5
|
+
# Compiles ERB templates into Ruby code; the compiled code produces the
|
|
6
|
+
# template result when evaluated. ERB::Compiler provides hooks to define how
|
|
7
|
+
# generated output is handled.
|
|
8
|
+
#
|
|
9
|
+
# Internally ERB does something like this to generate the code returned by
|
|
10
|
+
# ERB#src:
|
|
11
|
+
#
|
|
12
|
+
# compiler = ERB::Compiler.new('<>')
|
|
13
|
+
# compiler.pre_cmd = ["_erbout=+''"]
|
|
14
|
+
# compiler.put_cmd = "_erbout.<<"
|
|
15
|
+
# compiler.insert_cmd = "_erbout.<<"
|
|
16
|
+
# compiler.post_cmd = ["_erbout"]
|
|
17
|
+
#
|
|
18
|
+
# code, enc = compiler.compile("Got <%= obj %>!\n")
|
|
19
|
+
# puts code
|
|
20
|
+
#
|
|
21
|
+
# <i>Generates</i>:
|
|
22
|
+
#
|
|
23
|
+
# #coding:UTF-8
|
|
24
|
+
# _erbout=+''; _erbout.<< "Got ".freeze; _erbout.<<(( obj ).to_s); _erbout.<< "!\n".freeze; _erbout
|
|
25
|
+
#
|
|
26
|
+
# By default the output is sent to the print method. For example:
|
|
27
|
+
#
|
|
28
|
+
# compiler = ERB::Compiler.new('<>')
|
|
29
|
+
# code, enc = compiler.compile("Got <%= obj %>!\n")
|
|
30
|
+
# puts code
|
|
31
|
+
#
|
|
32
|
+
# <i>Generates</i>:
|
|
33
|
+
#
|
|
34
|
+
# #coding:UTF-8
|
|
35
|
+
# print "Got ".freeze; print(( obj ).to_s); print "!\n".freeze
|
|
36
|
+
#
|
|
37
|
+
# == Evaluation
|
|
38
|
+
#
|
|
39
|
+
# The compiled code can be used in any context where the names in the code
|
|
40
|
+
# correctly resolve. Using the last example, each of these print 'Got It!'
|
|
41
|
+
#
|
|
42
|
+
# Evaluate using a variable:
|
|
43
|
+
#
|
|
44
|
+
# obj = 'It'
|
|
45
|
+
# eval code
|
|
46
|
+
#
|
|
47
|
+
# Evaluate using an input:
|
|
48
|
+
#
|
|
49
|
+
# mod = Module.new
|
|
50
|
+
# mod.module_eval %{
|
|
51
|
+
# def get(obj)
|
|
52
|
+
# #{code}
|
|
53
|
+
# end
|
|
54
|
+
# }
|
|
55
|
+
# extend mod
|
|
56
|
+
# get('It')
|
|
57
|
+
#
|
|
58
|
+
# Evaluate using an accessor:
|
|
59
|
+
#
|
|
60
|
+
# klass = Class.new Object
|
|
61
|
+
# klass.class_eval %{
|
|
62
|
+
# attr_accessor :obj
|
|
63
|
+
# def initialize(obj)
|
|
64
|
+
# @obj = obj
|
|
65
|
+
# end
|
|
66
|
+
# def get_it
|
|
67
|
+
# #{code}
|
|
68
|
+
# end
|
|
69
|
+
# }
|
|
70
|
+
# klass.new('It').get_it
|
|
71
|
+
#
|
|
72
|
+
# Good! See also ERB#def_method, ERB#def_module, and ERB#def_class.
|
|
73
|
+
class ERB::Compiler # :nodoc:
|
|
74
|
+
class PercentLine # :nodoc:
|
|
75
|
+
def initialize(str)
|
|
76
|
+
@value = str
|
|
77
|
+
end
|
|
78
|
+
attr_reader :value
|
|
79
|
+
alias :to_s :value
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
class Scanner # :nodoc:
|
|
83
|
+
@scanner_map = defined?(Ractor) ? Ractor.make_shareable({}) : {}
|
|
84
|
+
class << self
|
|
85
|
+
if defined?(Ractor)
|
|
86
|
+
def register_scanner(klass, trim_mode, percent)
|
|
87
|
+
@scanner_map = Ractor.make_shareable({ **@scanner_map, [trim_mode, percent] => klass })
|
|
88
|
+
end
|
|
89
|
+
else
|
|
90
|
+
def register_scanner(klass, trim_mode, percent)
|
|
91
|
+
@scanner_map[[trim_mode, percent]] = klass
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
alias :regist_scanner :register_scanner
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.default_scanner=(klass)
|
|
98
|
+
@default_scanner = klass
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def self.make_scanner(src, trim_mode, percent)
|
|
102
|
+
klass = @scanner_map.fetch([trim_mode, percent], @default_scanner)
|
|
103
|
+
klass.new(src, trim_mode, percent)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
DEFAULT_STAGS = %w(<%% <%= <%# <%).freeze
|
|
107
|
+
DEFAULT_ETAGS = %w(%%> %>).freeze
|
|
108
|
+
def initialize(src, trim_mode, percent)
|
|
109
|
+
@src = src
|
|
110
|
+
@stag = nil
|
|
111
|
+
@stags = DEFAULT_STAGS
|
|
112
|
+
@etags = DEFAULT_ETAGS
|
|
113
|
+
end
|
|
114
|
+
attr_accessor :stag
|
|
115
|
+
attr_reader :stags, :etags
|
|
116
|
+
|
|
117
|
+
def scan; end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
class TrimScanner < Scanner # :nodoc:
|
|
121
|
+
def initialize(src, trim_mode, percent)
|
|
122
|
+
super
|
|
123
|
+
@trim_mode = trim_mode
|
|
124
|
+
@percent = percent
|
|
125
|
+
if @trim_mode == '>'
|
|
126
|
+
@scan_reg = /(.*?)(%>\r?\n|#{(stags + etags).join('|')}|\n|\z)/m
|
|
127
|
+
@scan_line = self.method(:trim_line1)
|
|
128
|
+
elsif @trim_mode == '<>'
|
|
129
|
+
@scan_reg = /(.*?)(%>\r?\n|#{(stags + etags).join('|')}|\n|\z)/m
|
|
130
|
+
@scan_line = self.method(:trim_line2)
|
|
131
|
+
elsif @trim_mode == '-'
|
|
132
|
+
@scan_reg = /(.*?)(^[ \t]*<%\-|<%\-|-%>\r?\n|-%>|#{(stags + etags).join('|')}|\z)/m
|
|
133
|
+
@scan_line = self.method(:explicit_trim_line)
|
|
134
|
+
else
|
|
135
|
+
@scan_reg = /(.*?)(#{(stags + etags).join('|')}|\n|\z)/m
|
|
136
|
+
@scan_line = self.method(:scan_line)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def scan(&block)
|
|
141
|
+
@stag = nil
|
|
142
|
+
if @percent
|
|
143
|
+
@src.each_line do |line|
|
|
144
|
+
percent_line(line, &block)
|
|
145
|
+
end
|
|
146
|
+
else
|
|
147
|
+
@scan_line.call(@src, &block)
|
|
148
|
+
end
|
|
149
|
+
nil
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def percent_line(line, &block)
|
|
153
|
+
if @stag || line[0] != ?%
|
|
154
|
+
return @scan_line.call(line, &block)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
line[0] = ''
|
|
158
|
+
if line[0] == ?%
|
|
159
|
+
@scan_line.call(line, &block)
|
|
160
|
+
else
|
|
161
|
+
yield(PercentLine.new(line.chomp))
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def scan_line(line)
|
|
166
|
+
line.scan(@scan_reg) do |tokens|
|
|
167
|
+
tokens.each do |token|
|
|
168
|
+
next if token.empty?
|
|
169
|
+
yield(token)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def trim_line1(line)
|
|
175
|
+
line.scan(@scan_reg) do |tokens|
|
|
176
|
+
tokens.each do |token|
|
|
177
|
+
next if token.empty?
|
|
178
|
+
if token == "%>\n" || token == "%>\r\n"
|
|
179
|
+
yield('%>')
|
|
180
|
+
yield(:cr)
|
|
181
|
+
else
|
|
182
|
+
yield(token)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def trim_line2(line)
|
|
189
|
+
head = nil
|
|
190
|
+
line.scan(@scan_reg) do |tokens|
|
|
191
|
+
tokens.each do |token|
|
|
192
|
+
next if token.empty?
|
|
193
|
+
head = token unless head
|
|
194
|
+
if token == "%>\n" || token == "%>\r\n"
|
|
195
|
+
yield('%>')
|
|
196
|
+
if is_erb_stag?(head)
|
|
197
|
+
yield(:cr)
|
|
198
|
+
else
|
|
199
|
+
yield("\n")
|
|
200
|
+
end
|
|
201
|
+
head = nil
|
|
202
|
+
else
|
|
203
|
+
yield(token)
|
|
204
|
+
head = nil if token == "\n"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def explicit_trim_line(line)
|
|
211
|
+
line.scan(@scan_reg) do |tokens|
|
|
212
|
+
tokens.each do |token|
|
|
213
|
+
next if token.empty?
|
|
214
|
+
if @stag.nil? && /[ \t]*<%-/ =~ token
|
|
215
|
+
yield('<%')
|
|
216
|
+
elsif @stag && (token == "-%>\n" || token == "-%>\r\n")
|
|
217
|
+
yield('%>')
|
|
218
|
+
yield(:cr)
|
|
219
|
+
elsif @stag && token == '-%>'
|
|
220
|
+
yield('%>')
|
|
221
|
+
else
|
|
222
|
+
yield(token)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
ERB_STAG = %w(<%= <%# <%)
|
|
229
|
+
def is_erb_stag?(s)
|
|
230
|
+
ERB_STAG.member?(s)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
Scanner.default_scanner = TrimScanner
|
|
235
|
+
|
|
236
|
+
begin
|
|
237
|
+
require 'strscan'
|
|
238
|
+
rescue LoadError
|
|
239
|
+
else
|
|
240
|
+
class SimpleScanner < Scanner # :nodoc:
|
|
241
|
+
def scan
|
|
242
|
+
stag_reg = (stags == DEFAULT_STAGS) ? /(.*?)(<%[%=#]?|\z)/m : /(.*?)(#{stags.join('|')}|\z)/m
|
|
243
|
+
etag_reg = (etags == DEFAULT_ETAGS) ? /(.*?)(%%?>|\z)/m : /(.*?)(#{etags.join('|')}|\z)/m
|
|
244
|
+
scanner = StringScanner.new(@src)
|
|
245
|
+
while ! scanner.eos?
|
|
246
|
+
scanner.scan(@stag ? etag_reg : stag_reg)
|
|
247
|
+
yield(scanner[1])
|
|
248
|
+
yield(scanner[2])
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
Scanner.register_scanner(SimpleScanner, nil, false)
|
|
253
|
+
|
|
254
|
+
class ExplicitScanner < Scanner # :nodoc:
|
|
255
|
+
def scan
|
|
256
|
+
stag_reg = /(.*?)(^[ \t]*<%-|<%-|#{stags.join('|')}|\z)/m
|
|
257
|
+
etag_reg = /(.*?)(-%>|#{etags.join('|')}|\z)/m
|
|
258
|
+
scanner = StringScanner.new(@src)
|
|
259
|
+
while ! scanner.eos?
|
|
260
|
+
scanner.scan(@stag ? etag_reg : stag_reg)
|
|
261
|
+
yield(scanner[1])
|
|
262
|
+
|
|
263
|
+
elem = scanner[2]
|
|
264
|
+
if /[ \t]*<%-/ =~ elem
|
|
265
|
+
yield('<%')
|
|
266
|
+
elsif elem == '-%>'
|
|
267
|
+
yield('%>')
|
|
268
|
+
yield(:cr) if scanner.scan(/(\r?\n|\z)/)
|
|
269
|
+
else
|
|
270
|
+
yield(elem)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
Scanner.register_scanner(ExplicitScanner, '-', false)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
class Buffer # :nodoc:
|
|
279
|
+
def initialize(compiler, enc=nil, frozen=nil)
|
|
280
|
+
@compiler = compiler
|
|
281
|
+
@line = []
|
|
282
|
+
@script = +''
|
|
283
|
+
@script << "#coding:#{enc}\n" if enc
|
|
284
|
+
@script << "#frozen-string-literal:#{frozen}\n" unless frozen.nil?
|
|
285
|
+
@compiler.pre_cmd.each do |x|
|
|
286
|
+
push(x)
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
attr_reader :script
|
|
290
|
+
|
|
291
|
+
def push(cmd)
|
|
292
|
+
@line << cmd
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def cr
|
|
296
|
+
@script << (@line.join('; '))
|
|
297
|
+
@line = []
|
|
298
|
+
@script << "\n"
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def close
|
|
302
|
+
return unless @line
|
|
303
|
+
@compiler.post_cmd.each do |x|
|
|
304
|
+
push(x)
|
|
305
|
+
end
|
|
306
|
+
@script << (@line.join('; '))
|
|
307
|
+
@line = nil
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def add_put_cmd(out, content)
|
|
312
|
+
out.push("#{@put_cmd} #{content.dump}.freeze#{"\n" * content.count("\n")}")
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def add_insert_cmd(out, content)
|
|
316
|
+
out.push("#{@insert_cmd}((#{content}).to_s)")
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Compiles an ERB template into Ruby code. Returns an array of the code
|
|
320
|
+
# and encoding like ["code", Encoding].
|
|
321
|
+
def compile(s)
|
|
322
|
+
enc = s.encoding
|
|
323
|
+
raise ArgumentError, "#{enc} is not ASCII compatible" if enc.dummy?
|
|
324
|
+
s = s.b # see String#b
|
|
325
|
+
magic_comment = detect_magic_comment(s, enc)
|
|
326
|
+
out = Buffer.new(self, *magic_comment)
|
|
327
|
+
|
|
328
|
+
self.content = +''
|
|
329
|
+
scanner = make_scanner(s)
|
|
330
|
+
scanner.scan do |token|
|
|
331
|
+
next if token.nil?
|
|
332
|
+
next if token == ''
|
|
333
|
+
if scanner.stag.nil?
|
|
334
|
+
compile_stag(token, out, scanner)
|
|
335
|
+
else
|
|
336
|
+
compile_etag(token, out, scanner)
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
add_put_cmd(out, content) if content.size > 0
|
|
340
|
+
out.close
|
|
341
|
+
return out.script, *magic_comment
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def compile_stag(stag, out, scanner)
|
|
345
|
+
case stag
|
|
346
|
+
when PercentLine
|
|
347
|
+
add_put_cmd(out, content) if content.size > 0
|
|
348
|
+
self.content = +''
|
|
349
|
+
out.push(stag.to_s)
|
|
350
|
+
out.cr
|
|
351
|
+
when :cr
|
|
352
|
+
out.cr
|
|
353
|
+
when '<%', '<%=', '<%#'
|
|
354
|
+
scanner.stag = stag
|
|
355
|
+
add_put_cmd(out, content) if content.size > 0
|
|
356
|
+
self.content = +''
|
|
357
|
+
when "\n"
|
|
358
|
+
content << "\n"
|
|
359
|
+
add_put_cmd(out, content)
|
|
360
|
+
self.content = +''
|
|
361
|
+
when '<%%'
|
|
362
|
+
content << '<%'
|
|
363
|
+
else
|
|
364
|
+
content << stag
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def compile_etag(etag, out, scanner)
|
|
369
|
+
case etag
|
|
370
|
+
when '%>'
|
|
371
|
+
compile_content(scanner.stag, out)
|
|
372
|
+
scanner.stag = nil
|
|
373
|
+
self.content = +''
|
|
374
|
+
when '%%>'
|
|
375
|
+
content << '%>'
|
|
376
|
+
else
|
|
377
|
+
content << etag
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def compile_content(stag, out)
|
|
382
|
+
case stag
|
|
383
|
+
when '<%'
|
|
384
|
+
if content[-1] == ?\n
|
|
385
|
+
content.chop!
|
|
386
|
+
out.push(content)
|
|
387
|
+
out.cr
|
|
388
|
+
else
|
|
389
|
+
out.push(content)
|
|
390
|
+
end
|
|
391
|
+
when '<%='
|
|
392
|
+
add_insert_cmd(out, content)
|
|
393
|
+
when '<%#'
|
|
394
|
+
out.push("\n" * content.count("\n")) # only adjust lineno
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def prepare_trim_mode(mode) # :nodoc:
|
|
399
|
+
case mode
|
|
400
|
+
when 1
|
|
401
|
+
return [false, '>']
|
|
402
|
+
when 2
|
|
403
|
+
return [false, '<>']
|
|
404
|
+
when 0, nil
|
|
405
|
+
return [false, nil]
|
|
406
|
+
when String
|
|
407
|
+
unless mode.match?(/\A(%|-|>|<>){1,2}\z/)
|
|
408
|
+
warn_invalid_trim_mode(mode, uplevel: 5)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
perc = mode.include?('%')
|
|
412
|
+
if mode.include?('-')
|
|
413
|
+
return [perc, '-']
|
|
414
|
+
elsif mode.include?('<>')
|
|
415
|
+
return [perc, '<>']
|
|
416
|
+
elsif mode.include?('>')
|
|
417
|
+
return [perc, '>']
|
|
418
|
+
else
|
|
419
|
+
[perc, nil]
|
|
420
|
+
end
|
|
421
|
+
else
|
|
422
|
+
warn_invalid_trim_mode(mode, uplevel: 5)
|
|
423
|
+
return [false, nil]
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def make_scanner(src) # :nodoc:
|
|
428
|
+
Scanner.make_scanner(src, @trim_mode, @percent)
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
# Construct a new compiler using the trim_mode. See ERB::new for available
|
|
432
|
+
# trim modes.
|
|
433
|
+
def initialize(trim_mode)
|
|
434
|
+
@percent, @trim_mode = prepare_trim_mode(trim_mode)
|
|
435
|
+
@put_cmd = 'print'
|
|
436
|
+
@insert_cmd = @put_cmd
|
|
437
|
+
@pre_cmd = []
|
|
438
|
+
@post_cmd = []
|
|
439
|
+
end
|
|
440
|
+
attr_reader :percent, :trim_mode
|
|
441
|
+
|
|
442
|
+
# The command to handle text that ends with a newline
|
|
443
|
+
attr_accessor :put_cmd
|
|
444
|
+
|
|
445
|
+
# The command to handle text that is inserted prior to a newline
|
|
446
|
+
attr_accessor :insert_cmd
|
|
447
|
+
|
|
448
|
+
# An array of commands prepended to compiled code
|
|
449
|
+
attr_accessor :pre_cmd
|
|
450
|
+
|
|
451
|
+
# An array of commands appended to compiled code
|
|
452
|
+
attr_accessor :post_cmd
|
|
453
|
+
|
|
454
|
+
private
|
|
455
|
+
|
|
456
|
+
# A buffered text in #compile
|
|
457
|
+
attr_accessor :content
|
|
458
|
+
|
|
459
|
+
def detect_magic_comment(s, enc = nil)
|
|
460
|
+
re = @percent ? /\G(?:<%#(.*)%>|%#(.*)\n)/ : /\G<%#(.*)%>/
|
|
461
|
+
frozen = nil
|
|
462
|
+
s.scan(re) do
|
|
463
|
+
comment = $+
|
|
464
|
+
comment = $1 if comment[/-\*-\s*([^\s].*?)\s*-\*-$/]
|
|
465
|
+
case comment
|
|
466
|
+
when %r"coding\s*[=:]\s*([[:alnum:]\-_]+)"
|
|
467
|
+
enc = Encoding.find($1.sub(/-(?:mac|dos|unix)/i, ''))
|
|
468
|
+
when %r"frozen[-_]string[-_]literal\s*:\s*([[:alnum:]]+)"
|
|
469
|
+
frozen = $1
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
return enc, frozen
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
# :stopdoc:
|
|
476
|
+
WARNING_UPLEVEL = Class.new {
|
|
477
|
+
attr_reader :c
|
|
478
|
+
def initialize from
|
|
479
|
+
@c = caller.length - from.length
|
|
480
|
+
end
|
|
481
|
+
}.new(caller(0)).c
|
|
482
|
+
private_constant :WARNING_UPLEVEL
|
|
483
|
+
# :startdoc:
|
|
484
|
+
|
|
485
|
+
def warn_invalid_trim_mode(mode, uplevel:)
|
|
486
|
+
warn "Invalid ERB trim mode: #{mode.inspect} (trim_mode: nil, 0, 1, 2, or String composed of '%' and/or '-', '>', '<>')", uplevel: uplevel + WARNING_UPLEVEL
|
|
487
|
+
end
|
|
488
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# ERB::DefMethod
|
|
4
|
+
#
|
|
5
|
+
# Utility module to define eRuby script as instance method.
|
|
6
|
+
#
|
|
7
|
+
# === Example
|
|
8
|
+
#
|
|
9
|
+
# example.rhtml:
|
|
10
|
+
# <% for item in @items %>
|
|
11
|
+
# <b><%= item %></b>
|
|
12
|
+
# <% end %>
|
|
13
|
+
#
|
|
14
|
+
# example.rb:
|
|
15
|
+
# require 'erb'
|
|
16
|
+
# class MyClass
|
|
17
|
+
# extend ERB::DefMethod
|
|
18
|
+
# def_erb_method('render()', 'example.rhtml')
|
|
19
|
+
# def initialize(items)
|
|
20
|
+
# @items = items
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
# print MyClass.new([10,20,30]).render()
|
|
24
|
+
#
|
|
25
|
+
# result:
|
|
26
|
+
#
|
|
27
|
+
# <b>10</b>
|
|
28
|
+
#
|
|
29
|
+
# <b>20</b>
|
|
30
|
+
#
|
|
31
|
+
# <b>30</b>
|
|
32
|
+
#
|
|
33
|
+
module ERB::DefMethod
|
|
34
|
+
# define _methodname_ as instance method of current module, using ERB
|
|
35
|
+
# object or eRuby file
|
|
36
|
+
def def_erb_method(methodname, erb_or_fname)
|
|
37
|
+
if erb_or_fname.kind_of? String
|
|
38
|
+
fname = erb_or_fname
|
|
39
|
+
erb = ERB.new(File.read(fname))
|
|
40
|
+
erb.def_method(self, methodname, fname)
|
|
41
|
+
else
|
|
42
|
+
erb = erb_or_fname
|
|
43
|
+
erb.def_method(self, methodname, erb.filename || '(ERB)')
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
module_function :def_erb_method
|
|
47
|
+
end
|
data/lib/erb/escape.so
ADDED
|
Binary file
|