actionmailer 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionmailer might be problematic. Click here for more details.
- data/CHANGELOG +3 -0
- data/MIT-LICENSE +21 -0
- data/README +102 -0
- data/install.rb +61 -0
- data/lib/action_mailer.rb +44 -0
- data/lib/action_mailer/base.rb +111 -0
- data/lib/action_mailer/mail_helper.rb +17 -0
- data/lib/action_mailer/vendor/text/format.rb +1447 -0
- data/lib/action_mailer/vendor/tmail.rb +4 -0
- data/lib/action_mailer/vendor/tmail/address.rb +223 -0
- data/lib/action_mailer/vendor/tmail/base64.rb +52 -0
- data/lib/action_mailer/vendor/tmail/config.rb +50 -0
- data/lib/action_mailer/vendor/tmail/encode.rb +447 -0
- data/lib/action_mailer/vendor/tmail/facade.rb +531 -0
- data/lib/action_mailer/vendor/tmail/header.rb +893 -0
- data/lib/action_mailer/vendor/tmail/info.rb +16 -0
- data/lib/action_mailer/vendor/tmail/loader.rb +1 -0
- data/lib/action_mailer/vendor/tmail/mail.rb +420 -0
- data/lib/action_mailer/vendor/tmail/mailbox.rb +414 -0
- data/lib/action_mailer/vendor/tmail/mbox.rb +1 -0
- data/lib/action_mailer/vendor/tmail/net.rb +261 -0
- data/lib/action_mailer/vendor/tmail/obsolete.rb +116 -0
- data/lib/action_mailer/vendor/tmail/parser.rb +1503 -0
- data/lib/action_mailer/vendor/tmail/port.rb +358 -0
- data/lib/action_mailer/vendor/tmail/scanner.rb +22 -0
- data/lib/action_mailer/vendor/tmail/scanner_r.rb +244 -0
- data/lib/action_mailer/vendor/tmail/stringio.rb +260 -0
- data/lib/action_mailer/vendor/tmail/tmail.rb +1 -0
- data/lib/action_mailer/vendor/tmail/utils.rb +215 -0
- data/rakefile +99 -0
- data/test/fixtures/templates/signed_up.rhtml +3 -0
- data/test/fixtures/test_mailer/signed_up.rhtml +3 -0
- data/test/mail_service_test.rb +53 -0
- metadata +86 -0
@@ -0,0 +1,358 @@
|
|
1
|
+
#
|
2
|
+
# port.rb
|
3
|
+
#
|
4
|
+
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
5
|
+
#
|
6
|
+
# This program is free software.
|
7
|
+
# You can distribute/modify this program under the terms of
|
8
|
+
# the GNU Lesser General Public License version 2 or later.
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'tmail/stringio'
|
12
|
+
|
13
|
+
|
14
|
+
module TMail
|
15
|
+
|
16
|
+
class Port
|
17
|
+
def reproducible?
|
18
|
+
false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
###
|
24
|
+
### FilePort
|
25
|
+
###
|
26
|
+
|
27
|
+
class FilePort < Port
|
28
|
+
|
29
|
+
def initialize( fname )
|
30
|
+
@filename = File.expand_path(fname)
|
31
|
+
super()
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :filename
|
35
|
+
|
36
|
+
alias ident filename
|
37
|
+
|
38
|
+
def ==( other )
|
39
|
+
other.respond_to?(:filename) and @filename == other.filename
|
40
|
+
end
|
41
|
+
|
42
|
+
alias eql? ==
|
43
|
+
|
44
|
+
def hash
|
45
|
+
@filename.hash
|
46
|
+
end
|
47
|
+
|
48
|
+
def inspect
|
49
|
+
"#<#{self.class}:#{@filename}>"
|
50
|
+
end
|
51
|
+
|
52
|
+
def reproducible?
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
def size
|
57
|
+
File.size @filename
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def ropen( &block )
|
62
|
+
File.open(@filename, &block)
|
63
|
+
end
|
64
|
+
|
65
|
+
def wopen( &block )
|
66
|
+
File.open(@filename, 'w', &block)
|
67
|
+
end
|
68
|
+
|
69
|
+
def aopen( &block )
|
70
|
+
File.open(@filename, 'a', &block)
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def read_all
|
75
|
+
ropen {|f|
|
76
|
+
return f.read
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
def remove
|
82
|
+
File.unlink @filename
|
83
|
+
end
|
84
|
+
|
85
|
+
def move_to( port )
|
86
|
+
begin
|
87
|
+
File.link @filename, port.filename
|
88
|
+
rescue Errno::EXDEV
|
89
|
+
copy_to port
|
90
|
+
end
|
91
|
+
File.unlink @filename
|
92
|
+
end
|
93
|
+
|
94
|
+
alias mv move_to
|
95
|
+
|
96
|
+
def copy_to( port )
|
97
|
+
if FilePort === port
|
98
|
+
copy_file @filename, port.filename
|
99
|
+
else
|
100
|
+
File.open(@filename) {|r|
|
101
|
+
port.wopen {|w|
|
102
|
+
while s = r.sysread(4096)
|
103
|
+
w.write << s
|
104
|
+
end
|
105
|
+
} }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
alias cp copy_to
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
# from fileutils.rb
|
114
|
+
def copy_file( src, dest )
|
115
|
+
st = r = w = nil
|
116
|
+
|
117
|
+
File.open(src, 'rb') {|r|
|
118
|
+
File.open(dest, 'wb') {|w|
|
119
|
+
st = r.stat
|
120
|
+
begin
|
121
|
+
while true
|
122
|
+
w.write r.sysread(st.blksize)
|
123
|
+
end
|
124
|
+
rescue EOFError
|
125
|
+
end
|
126
|
+
} }
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
module MailFlags
|
133
|
+
|
134
|
+
def seen=( b )
|
135
|
+
set_status 'S', b
|
136
|
+
end
|
137
|
+
|
138
|
+
def seen?
|
139
|
+
get_status 'S'
|
140
|
+
end
|
141
|
+
|
142
|
+
def replied=( b )
|
143
|
+
set_status 'R', b
|
144
|
+
end
|
145
|
+
|
146
|
+
def replied?
|
147
|
+
get_status 'R'
|
148
|
+
end
|
149
|
+
|
150
|
+
def flagged=( b )
|
151
|
+
set_status 'F', b
|
152
|
+
end
|
153
|
+
|
154
|
+
def flagged?
|
155
|
+
get_status 'F'
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
def procinfostr( str, tag, true_p )
|
161
|
+
a = str.upcase.split(//)
|
162
|
+
a.push true_p ? tag : nil
|
163
|
+
a.delete tag unless true_p
|
164
|
+
a.compact.sort.join('').squeeze
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
class MhPort < FilePort
|
171
|
+
|
172
|
+
include MailFlags
|
173
|
+
|
174
|
+
private
|
175
|
+
|
176
|
+
def set_status( tag, flag )
|
177
|
+
begin
|
178
|
+
tmpfile = @filename + '.tmailtmp.' + $$.to_s
|
179
|
+
File.open(tmpfile, 'w') {|f|
|
180
|
+
write_status f, tag, flag
|
181
|
+
}
|
182
|
+
File.unlink @filename
|
183
|
+
File.link tmpfile, @filename
|
184
|
+
ensure
|
185
|
+
File.unlink tmpfile
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def write_status( f, tag, flag )
|
190
|
+
stat = ''
|
191
|
+
File.open(@filename) {|r|
|
192
|
+
while line = r.gets
|
193
|
+
if line.strip.empty?
|
194
|
+
break
|
195
|
+
elsif m = /\AX-TMail-Status:/i.match(line)
|
196
|
+
stat = m.post_match.strip
|
197
|
+
else
|
198
|
+
f.print line
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
s = procinfostr(stat, tag, flag)
|
203
|
+
f.puts 'X-TMail-Status: ' + s unless s.empty?
|
204
|
+
f.puts
|
205
|
+
|
206
|
+
while s = r.read(2048)
|
207
|
+
f.write s
|
208
|
+
end
|
209
|
+
}
|
210
|
+
end
|
211
|
+
|
212
|
+
def get_status( tag )
|
213
|
+
File.foreach(@filename) {|line|
|
214
|
+
return false if line.strip.empty?
|
215
|
+
if m = /\AX-TMail-Status:/i.match(line)
|
216
|
+
return m.post_match.strip.include?(tag[0])
|
217
|
+
end
|
218
|
+
}
|
219
|
+
false
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
|
224
|
+
|
225
|
+
class MaildirPort < FilePort
|
226
|
+
|
227
|
+
def move_to_new
|
228
|
+
new = replace_dir(@filename, 'new')
|
229
|
+
File.rename @filename, new
|
230
|
+
@filename = new
|
231
|
+
end
|
232
|
+
|
233
|
+
def move_to_cur
|
234
|
+
new = replace_dir(@filename, 'cur')
|
235
|
+
File.rename @filename, new
|
236
|
+
@filename = new
|
237
|
+
end
|
238
|
+
|
239
|
+
def replace_dir( path, dir )
|
240
|
+
"#{File.dirname File.dirname(path)}/#{dir}/#{File.basename path}"
|
241
|
+
end
|
242
|
+
private :replace_dir
|
243
|
+
|
244
|
+
|
245
|
+
include MailFlags
|
246
|
+
|
247
|
+
private
|
248
|
+
|
249
|
+
MAIL_FILE = /\A(\d+\.[\d_]+\.[^:]+)(?:\:(\d),(\w+)?)?\z/
|
250
|
+
|
251
|
+
def set_status( tag, flag )
|
252
|
+
if m = MAIL_FILE.match(File.basename(@filename))
|
253
|
+
s, uniq, type, info, = m.to_a
|
254
|
+
return if type and type != '2' # do not change anything
|
255
|
+
newname = File.dirname(@filename) + '/' +
|
256
|
+
uniq + ':2,' + procinfostr(info.to_s, tag, flag)
|
257
|
+
else
|
258
|
+
newname = @filename + ':2,' + tag
|
259
|
+
end
|
260
|
+
|
261
|
+
File.link @filename, newname
|
262
|
+
File.unlink @filename
|
263
|
+
@filename = newname
|
264
|
+
end
|
265
|
+
|
266
|
+
def get_status( tag )
|
267
|
+
m = MAIL_FILE.match(File.basename(@filename)) or return false
|
268
|
+
m[2] == '2' and m[3].to_s.include?(tag[0])
|
269
|
+
end
|
270
|
+
|
271
|
+
end
|
272
|
+
|
273
|
+
|
274
|
+
###
|
275
|
+
### StringPort
|
276
|
+
###
|
277
|
+
|
278
|
+
class StringPort < Port
|
279
|
+
|
280
|
+
def initialize( str = '' )
|
281
|
+
@buffer = str
|
282
|
+
super()
|
283
|
+
end
|
284
|
+
|
285
|
+
def string
|
286
|
+
@buffer
|
287
|
+
end
|
288
|
+
|
289
|
+
def to_s
|
290
|
+
@buffer.dup
|
291
|
+
end
|
292
|
+
|
293
|
+
alias read_all to_s
|
294
|
+
|
295
|
+
def size
|
296
|
+
@buffer.size
|
297
|
+
end
|
298
|
+
|
299
|
+
def ==( other )
|
300
|
+
StringPort === other and @buffer.equal? other.string
|
301
|
+
end
|
302
|
+
|
303
|
+
alias eql? ==
|
304
|
+
|
305
|
+
def hash
|
306
|
+
@buffer.id.hash
|
307
|
+
end
|
308
|
+
|
309
|
+
def inspect
|
310
|
+
"#<#{self.class}:id=#{sprintf '0x%x', @buffer.id}>"
|
311
|
+
end
|
312
|
+
|
313
|
+
def reproducible?
|
314
|
+
true
|
315
|
+
end
|
316
|
+
|
317
|
+
def ropen( &block )
|
318
|
+
@buffer or raise Errno::ENOENT, "#{inspect} is already removed"
|
319
|
+
StringInput.open(@buffer, &block)
|
320
|
+
end
|
321
|
+
|
322
|
+
def wopen( &block )
|
323
|
+
@buffer = ''
|
324
|
+
StringOutput.new(@buffer, &block)
|
325
|
+
end
|
326
|
+
|
327
|
+
def aopen( &block )
|
328
|
+
@buffer ||= ''
|
329
|
+
StringOutput.new(@buffer, &block)
|
330
|
+
end
|
331
|
+
|
332
|
+
def remove
|
333
|
+
@buffer = nil
|
334
|
+
end
|
335
|
+
|
336
|
+
alias rm remove
|
337
|
+
|
338
|
+
def copy_to( port )
|
339
|
+
port.wopen {|f|
|
340
|
+
f.write @buffer
|
341
|
+
}
|
342
|
+
end
|
343
|
+
|
344
|
+
alias cp copy_to
|
345
|
+
|
346
|
+
def move_to( port )
|
347
|
+
if StringPort === port
|
348
|
+
str = @buffer
|
349
|
+
port.instance_eval { @buffer = str }
|
350
|
+
else
|
351
|
+
copy_to port
|
352
|
+
end
|
353
|
+
remove
|
354
|
+
end
|
355
|
+
|
356
|
+
end
|
357
|
+
|
358
|
+
end # module TMail
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#
|
2
|
+
# scanner.rb
|
3
|
+
#
|
4
|
+
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
5
|
+
#
|
6
|
+
# This program is free software.
|
7
|
+
# You can distribute/modify this program under the terms of
|
8
|
+
# the GNU Lesser General Public License version 2 or later.
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'tmail/utils'
|
12
|
+
|
13
|
+
module TMail
|
14
|
+
require 'tmail/scanner_r.rb'
|
15
|
+
begin
|
16
|
+
raise LoadError, 'Turn off Ruby extention by user choice' if ENV['NORUBYEXT']
|
17
|
+
require 'tmail/scanner_c.so'
|
18
|
+
Scanner = Scanner_C
|
19
|
+
rescue LoadError
|
20
|
+
Scanner = Scanner_R
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
#
|
2
|
+
# scanner_r.rb
|
3
|
+
#
|
4
|
+
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
5
|
+
#
|
6
|
+
# This program is free software.
|
7
|
+
# You can distribute/modify this program under the terms of
|
8
|
+
# the GNU Lesser General Public License version 2 or later.
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'tmail/config'
|
12
|
+
|
13
|
+
|
14
|
+
module TMail
|
15
|
+
|
16
|
+
class Scanner_R
|
17
|
+
|
18
|
+
Version = '0.10.7'
|
19
|
+
Version.freeze
|
20
|
+
|
21
|
+
MIME_HEADERS = {
|
22
|
+
:CTYPE => true,
|
23
|
+
:CENCODING => true,
|
24
|
+
:CDISPOSITION => true
|
25
|
+
}
|
26
|
+
|
27
|
+
alnum = 'a-zA-Z0-9'
|
28
|
+
atomsyms = %q[ _#!$%&`'*+-{|}~^/=? ].strip
|
29
|
+
tokensyms = %q[ _#!$%&`'*+-{|}~^. ].strip
|
30
|
+
|
31
|
+
atomchars = alnum + Regexp.quote(atomsyms)
|
32
|
+
tokenchars = alnum + Regexp.quote(tokensyms)
|
33
|
+
iso2022str = '\e(?!\(B)..(?:[^\e]+|\e(?!\(B)..)*\e\(B'
|
34
|
+
|
35
|
+
eucstr = '(?:[\xa1-\xfe][\xa1-\xfe])+'
|
36
|
+
sjisstr = '(?:[\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc])+'
|
37
|
+
utf8str = '(?:[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf][\x80-\xbf])+'
|
38
|
+
|
39
|
+
quoted_with_iso2022 = /\A(?:[^\\\e"]+|#{iso2022str})+/n
|
40
|
+
domlit_with_iso2022 = /\A(?:[^\\\e\]]+|#{iso2022str})+/n
|
41
|
+
comment_with_iso2022 = /\A(?:[^\\\e()]+|#{iso2022str})+/n
|
42
|
+
|
43
|
+
quoted_without_iso2022 = /\A[^\\"]+/n
|
44
|
+
domlit_without_iso2022 = /\A[^\\\]]+/n
|
45
|
+
comment_without_iso2022 = /\A[^\\()]+/n
|
46
|
+
|
47
|
+
PATTERN_TABLE = {}
|
48
|
+
PATTERN_TABLE['EUC'] =
|
49
|
+
[
|
50
|
+
/\A(?:[#{atomchars}]+|#{iso2022str}|#{eucstr})+/n,
|
51
|
+
/\A(?:[#{tokenchars}]+|#{iso2022str}|#{eucstr})+/n,
|
52
|
+
quoted_with_iso2022,
|
53
|
+
domlit_with_iso2022,
|
54
|
+
comment_with_iso2022
|
55
|
+
]
|
56
|
+
PATTERN_TABLE['SJIS'] =
|
57
|
+
[
|
58
|
+
/\A(?:[#{atomchars}]+|#{iso2022str}|#{sjisstr})+/n,
|
59
|
+
/\A(?:[#{tokenchars}]+|#{iso2022str}|#{sjisstr})+/n,
|
60
|
+
quoted_with_iso2022,
|
61
|
+
domlit_with_iso2022,
|
62
|
+
comment_with_iso2022
|
63
|
+
]
|
64
|
+
PATTERN_TABLE['UTF8'] =
|
65
|
+
[
|
66
|
+
/\A(?:[#{atomchars}]+|#{utf8str})+/n,
|
67
|
+
/\A(?:[#{tokenchars}]+|#{utf8str})+/n,
|
68
|
+
quoted_without_iso2022,
|
69
|
+
domlit_without_iso2022,
|
70
|
+
comment_without_iso2022
|
71
|
+
]
|
72
|
+
PATTERN_TABLE['NONE'] =
|
73
|
+
[
|
74
|
+
/\A[#{atomchars}]+/n,
|
75
|
+
/\A[#{tokenchars}]+/n,
|
76
|
+
quoted_without_iso2022,
|
77
|
+
domlit_without_iso2022,
|
78
|
+
comment_without_iso2022
|
79
|
+
]
|
80
|
+
|
81
|
+
|
82
|
+
def initialize( str, scantype, comments )
|
83
|
+
init_scanner str
|
84
|
+
@comments = comments || []
|
85
|
+
@debug = false
|
86
|
+
|
87
|
+
# fix scanner mode
|
88
|
+
@received = (scantype == :RECEIVED)
|
89
|
+
@is_mime_header = MIME_HEADERS[scantype]
|
90
|
+
|
91
|
+
atom, token, @quoted_re, @domlit_re, @comment_re = PATTERN_TABLE[$KCODE]
|
92
|
+
@word_re = (MIME_HEADERS[scantype] ? token : atom)
|
93
|
+
end
|
94
|
+
|
95
|
+
attr_accessor :debug
|
96
|
+
|
97
|
+
def scan( &block )
|
98
|
+
if @debug
|
99
|
+
scan_main do |arr|
|
100
|
+
s, v = arr
|
101
|
+
printf "%7d %-10s %s\n",
|
102
|
+
rest_size(),
|
103
|
+
s.respond_to?(:id2name) ? s.id2name : s.inspect,
|
104
|
+
v.inspect
|
105
|
+
yield arr
|
106
|
+
end
|
107
|
+
else
|
108
|
+
scan_main(&block)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
RECV_TOKEN = {
|
115
|
+
'from' => :FROM,
|
116
|
+
'by' => :BY,
|
117
|
+
'via' => :VIA,
|
118
|
+
'with' => :WITH,
|
119
|
+
'id' => :ID,
|
120
|
+
'for' => :FOR
|
121
|
+
}
|
122
|
+
|
123
|
+
def scan_main
|
124
|
+
until eof?
|
125
|
+
if skip(/\A[\n\r\t ]+/n) # LWSP
|
126
|
+
break if eof?
|
127
|
+
end
|
128
|
+
|
129
|
+
if s = readstr(@word_re)
|
130
|
+
if @is_mime_header
|
131
|
+
yield :TOKEN, s
|
132
|
+
else
|
133
|
+
# atom
|
134
|
+
if /\A\d+\z/ === s
|
135
|
+
yield :DIGIT, s
|
136
|
+
elsif @received
|
137
|
+
yield RECV_TOKEN[s.downcase] || :ATOM, s
|
138
|
+
else
|
139
|
+
yield :ATOM, s
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
elsif skip(/\A"/)
|
144
|
+
yield :QUOTED, scan_quoted_word()
|
145
|
+
|
146
|
+
elsif skip(/\A\[/)
|
147
|
+
yield :DOMLIT, scan_domain_literal()
|
148
|
+
|
149
|
+
elsif skip(/\A\(/)
|
150
|
+
@comments.push scan_comment()
|
151
|
+
|
152
|
+
else
|
153
|
+
c = readchar()
|
154
|
+
yield c, c
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
yield false, '$'
|
159
|
+
end
|
160
|
+
|
161
|
+
def scan_quoted_word
|
162
|
+
scan_qstr(@quoted_re, /\A"/, 'quoted-word')
|
163
|
+
end
|
164
|
+
|
165
|
+
def scan_domain_literal
|
166
|
+
'[' + scan_qstr(@domlit_re, /\A\]/, 'domain-literal') + ']'
|
167
|
+
end
|
168
|
+
|
169
|
+
def scan_qstr( pattern, terminal, type )
|
170
|
+
result = ''
|
171
|
+
until eof?
|
172
|
+
if s = readstr(pattern) then result << s
|
173
|
+
elsif skip(terminal) then return result
|
174
|
+
elsif skip(/\A\\/) then result << readchar()
|
175
|
+
else
|
176
|
+
raise "TMail FATAL: not match in #{type}"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
scan_error! "found unterminated #{type}"
|
180
|
+
end
|
181
|
+
|
182
|
+
def scan_comment
|
183
|
+
result = ''
|
184
|
+
nest = 1
|
185
|
+
content = @comment_re
|
186
|
+
|
187
|
+
until eof?
|
188
|
+
if s = readstr(content) then result << s
|
189
|
+
elsif skip(/\A\)/) then nest -= 1
|
190
|
+
return result if nest == 0
|
191
|
+
result << ')'
|
192
|
+
elsif skip(/\A\(/) then nest += 1
|
193
|
+
result << '('
|
194
|
+
elsif skip(/\A\\/) then result << readchar()
|
195
|
+
else
|
196
|
+
raise 'TMail FATAL: not match in comment'
|
197
|
+
end
|
198
|
+
end
|
199
|
+
scan_error! 'found unterminated comment'
|
200
|
+
end
|
201
|
+
|
202
|
+
# string scanner
|
203
|
+
|
204
|
+
def init_scanner( str )
|
205
|
+
@src = str
|
206
|
+
end
|
207
|
+
|
208
|
+
def eof?
|
209
|
+
@src.empty?
|
210
|
+
end
|
211
|
+
|
212
|
+
def rest_size
|
213
|
+
@src.size
|
214
|
+
end
|
215
|
+
|
216
|
+
def readstr( re )
|
217
|
+
if m = re.match(@src)
|
218
|
+
@src = m.post_match
|
219
|
+
m[0]
|
220
|
+
else
|
221
|
+
nil
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def readchar
|
226
|
+
readstr(/\A./)
|
227
|
+
end
|
228
|
+
|
229
|
+
def skip( re )
|
230
|
+
if m = re.match(@src)
|
231
|
+
@src = m.post_match
|
232
|
+
true
|
233
|
+
else
|
234
|
+
false
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def scan_error!( msg )
|
239
|
+
raise SyntaxError, msg
|
240
|
+
end
|
241
|
+
|
242
|
+
end
|
243
|
+
|
244
|
+
end # module TMail
|