rumbster 1.0.0
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.
- data/COPYING +515 -0
- data/README +56 -0
- data/Rakefile +12 -0
- data/lib/message_observers.rb +40 -0
- data/lib/rumbster.rb +24 -0
- data/lib/smtp_protocol.rb +42 -0
- data/lib/smtp_states.rb +159 -0
- data/test/message_observers_test.rb +102 -0
- data/test/rumbster_test.rb +69 -0
- data/test/smtp_protocol_test.rb +64 -0
- data/test/smtp_states_test.rb +217 -0
- data/vendor/tmail.rb +4 -0
- data/vendor/tmail/.cvsignore +3 -0
- data/vendor/tmail/Makefile +19 -0
- data/vendor/tmail/address.rb +222 -0
- data/vendor/tmail/base64.rb +52 -0
- data/vendor/tmail/compat.rb +39 -0
- data/vendor/tmail/config.rb +50 -0
- data/vendor/tmail/encode.rb +447 -0
- data/vendor/tmail/header.rb +895 -0
- data/vendor/tmail/info.rb +14 -0
- data/vendor/tmail/loader.rb +1 -0
- data/vendor/tmail/mail.rb +869 -0
- data/vendor/tmail/mailbox.rb +386 -0
- data/vendor/tmail/mbox.rb +1 -0
- data/vendor/tmail/net.rb +260 -0
- data/vendor/tmail/obsolete.rb +122 -0
- data/vendor/tmail/parser.rb +1475 -0
- data/vendor/tmail/parser.y +372 -0
- data/vendor/tmail/port.rb +356 -0
- data/vendor/tmail/scanner.rb +22 -0
- data/vendor/tmail/scanner_r.rb +243 -0
- data/vendor/tmail/stringio.rb +256 -0
- data/vendor/tmail/textutils.rb +197 -0
- data/vendor/tmail/tmail.rb +1 -0
- data/vendor/tmail/utils.rb +23 -0
- metadata +88 -0
@@ -0,0 +1,372 @@
|
|
1
|
+
#
|
2
|
+
# parser.y
|
3
|
+
#
|
4
|
+
# Copyright (c) 1998-2004 Minero Aoki
|
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.1.
|
9
|
+
#
|
10
|
+
|
11
|
+
class TMail::Parser
|
12
|
+
|
13
|
+
options no_result_var
|
14
|
+
|
15
|
+
rule
|
16
|
+
|
17
|
+
content : DATETIME datetime { val[1] }
|
18
|
+
| RECEIVED received { val[1] }
|
19
|
+
| MADDRESS addrs_TOP { val[1] }
|
20
|
+
| RETPATH retpath { val[1] }
|
21
|
+
| KEYWORDS keys { val[1] }
|
22
|
+
| ENCRYPTED enc { val[1] }
|
23
|
+
| MIMEVERSION version { val[1] }
|
24
|
+
| CTYPE ctype { val[1] }
|
25
|
+
| CENCODING cencode { val[1] }
|
26
|
+
| CDISPOSITION cdisp { val[1] }
|
27
|
+
| ADDRESS addr_TOP { val[1] }
|
28
|
+
| MAILBOX mbox { val[1] }
|
29
|
+
|
30
|
+
datetime : day DIGIT ATOM DIGIT hour zone
|
31
|
+
# 0 1 2 3 4 5
|
32
|
+
# date month year
|
33
|
+
{
|
34
|
+
t = Time.gm(val[3].to_i, val[2], val[1].to_i, 0, 0, 0)
|
35
|
+
(t + val[4] - val[5]).localtime
|
36
|
+
}
|
37
|
+
|
38
|
+
day : /* none */
|
39
|
+
| ATOM ','
|
40
|
+
|
41
|
+
hour : DIGIT ':' DIGIT
|
42
|
+
{
|
43
|
+
(val[0].to_i * 60 * 60) +
|
44
|
+
(val[2].to_i * 60)
|
45
|
+
}
|
46
|
+
| DIGIT ':' DIGIT ':' DIGIT
|
47
|
+
{
|
48
|
+
(val[0].to_i * 60 * 60) +
|
49
|
+
(val[2].to_i * 60) +
|
50
|
+
(val[4].to_i)
|
51
|
+
}
|
52
|
+
|
53
|
+
zone : ATOM
|
54
|
+
{
|
55
|
+
timezone_string_to_unixtime(val[0])
|
56
|
+
}
|
57
|
+
|
58
|
+
received : from by via with id for received_datetime
|
59
|
+
{
|
60
|
+
val
|
61
|
+
}
|
62
|
+
|
63
|
+
from : /* none */
|
64
|
+
| FROM received_domain
|
65
|
+
{
|
66
|
+
val[1]
|
67
|
+
}
|
68
|
+
|
69
|
+
by : /* none */
|
70
|
+
| BY received_domain
|
71
|
+
{
|
72
|
+
val[1]
|
73
|
+
}
|
74
|
+
|
75
|
+
received_domain
|
76
|
+
: domain
|
77
|
+
{
|
78
|
+
join_domain(val[0])
|
79
|
+
}
|
80
|
+
| domain '@' domain
|
81
|
+
{
|
82
|
+
join_domain(val[2])
|
83
|
+
}
|
84
|
+
| domain DOMLIT
|
85
|
+
{
|
86
|
+
join_domain(val[0])
|
87
|
+
}
|
88
|
+
|
89
|
+
via : /* none */
|
90
|
+
| VIA ATOM
|
91
|
+
{
|
92
|
+
val[1]
|
93
|
+
}
|
94
|
+
|
95
|
+
with : /* none */
|
96
|
+
{
|
97
|
+
[]
|
98
|
+
}
|
99
|
+
| with WITH ATOM
|
100
|
+
{
|
101
|
+
val[0].push val[2]
|
102
|
+
val[0]
|
103
|
+
}
|
104
|
+
|
105
|
+
id : /* none */
|
106
|
+
| ID msgid
|
107
|
+
{
|
108
|
+
val[1]
|
109
|
+
}
|
110
|
+
| ID ATOM
|
111
|
+
{
|
112
|
+
val[1]
|
113
|
+
}
|
114
|
+
|
115
|
+
for : /* none */
|
116
|
+
| FOR received_addrspec
|
117
|
+
{
|
118
|
+
val[1]
|
119
|
+
}
|
120
|
+
|
121
|
+
received_addrspec
|
122
|
+
: routeaddr
|
123
|
+
{
|
124
|
+
val[0].spec
|
125
|
+
}
|
126
|
+
| spec
|
127
|
+
{
|
128
|
+
val[0].spec
|
129
|
+
}
|
130
|
+
|
131
|
+
received_datetime
|
132
|
+
: /* none */
|
133
|
+
| ';' datetime
|
134
|
+
{
|
135
|
+
val[1]
|
136
|
+
}
|
137
|
+
|
138
|
+
addrs_TOP : addrs
|
139
|
+
| group_bare
|
140
|
+
| addrs commas group_bare
|
141
|
+
|
142
|
+
addr_TOP : mbox
|
143
|
+
| group
|
144
|
+
| group_bare
|
145
|
+
|
146
|
+
retpath : addrs_TOP
|
147
|
+
| '<' '>' { [ Address.new(nil, nil) ] }
|
148
|
+
|
149
|
+
addrs : addr { val }
|
150
|
+
| addrs commas addr { val[0].push val[2]; val[0] }
|
151
|
+
|
152
|
+
addr : mbox
|
153
|
+
| group
|
154
|
+
|
155
|
+
mboxes : mbox
|
156
|
+
{
|
157
|
+
val
|
158
|
+
}
|
159
|
+
| mboxes commas mbox
|
160
|
+
{
|
161
|
+
val[0].push val[2]
|
162
|
+
val[0]
|
163
|
+
}
|
164
|
+
|
165
|
+
mbox : spec
|
166
|
+
| routeaddr
|
167
|
+
| addr_phrase routeaddr
|
168
|
+
{
|
169
|
+
val[1].phrase = Decoder.decode(val[0])
|
170
|
+
val[1]
|
171
|
+
}
|
172
|
+
|
173
|
+
group : group_bare ';'
|
174
|
+
|
175
|
+
group_bare: addr_phrase ':' mboxes
|
176
|
+
{
|
177
|
+
AddressGroup.new(val[0], val[2])
|
178
|
+
}
|
179
|
+
| addr_phrase ':' { AddressGroup.new(val[0], []) }
|
180
|
+
|
181
|
+
addr_phrase
|
182
|
+
: local_head { val[0].join('.') }
|
183
|
+
| addr_phrase local_head { val[0] << ' ' << val[1].join('.') }
|
184
|
+
|
185
|
+
routeaddr : '<' routes spec '>'
|
186
|
+
{
|
187
|
+
val[2].routes.replace val[1]
|
188
|
+
val[2]
|
189
|
+
}
|
190
|
+
| '<' spec '>'
|
191
|
+
{
|
192
|
+
val[1]
|
193
|
+
}
|
194
|
+
|
195
|
+
routes : at_domains ':'
|
196
|
+
|
197
|
+
at_domains: '@' domain { [ val[1].join('.') ] }
|
198
|
+
| at_domains ',' '@' domain { val[0].push val[3].join('.'); val[0] }
|
199
|
+
|
200
|
+
spec : local '@' domain { Address.new( val[0], val[2] ) }
|
201
|
+
| local { Address.new( val[0], nil ) }
|
202
|
+
|
203
|
+
local: local_head
|
204
|
+
| local_head '.' { val[0].push ''; val[0] }
|
205
|
+
|
206
|
+
local_head: word
|
207
|
+
{ val }
|
208
|
+
| local_head dots word
|
209
|
+
{
|
210
|
+
val[1].times do
|
211
|
+
val[0].push ''
|
212
|
+
end
|
213
|
+
val[0].push val[2]
|
214
|
+
val[0]
|
215
|
+
}
|
216
|
+
|
217
|
+
domain : domword
|
218
|
+
{ val }
|
219
|
+
| domain dots domword
|
220
|
+
{
|
221
|
+
val[1].times do
|
222
|
+
val[0].push ''
|
223
|
+
end
|
224
|
+
val[0].push val[2]
|
225
|
+
val[0]
|
226
|
+
}
|
227
|
+
|
228
|
+
dots : '.' { 0 }
|
229
|
+
| '.' '.' { 1 }
|
230
|
+
|
231
|
+
word : atom
|
232
|
+
| QUOTED
|
233
|
+
| DIGIT
|
234
|
+
|
235
|
+
domword : atom
|
236
|
+
| DOMLIT
|
237
|
+
| DIGIT
|
238
|
+
|
239
|
+
commas : ','
|
240
|
+
| commas ','
|
241
|
+
|
242
|
+
msgid : '<' spec '>'
|
243
|
+
{
|
244
|
+
val[1] = val[1].spec
|
245
|
+
val.join('')
|
246
|
+
}
|
247
|
+
|
248
|
+
keys : phrase { val }
|
249
|
+
| keys ',' phrase { val[0].push val[2]; val[0] }
|
250
|
+
|
251
|
+
phrase : word
|
252
|
+
| phrase word { val[0] << ' ' << val[1] }
|
253
|
+
|
254
|
+
enc : word
|
255
|
+
{
|
256
|
+
val.push nil
|
257
|
+
val
|
258
|
+
}
|
259
|
+
| word word
|
260
|
+
{
|
261
|
+
val
|
262
|
+
}
|
263
|
+
|
264
|
+
version : DIGIT '.' DIGIT
|
265
|
+
{
|
266
|
+
[ val[0].to_i, val[2].to_i ]
|
267
|
+
}
|
268
|
+
|
269
|
+
ctype : TOKEN '/' TOKEN params opt_semicolon
|
270
|
+
{
|
271
|
+
[ val[0].downcase, val[2].downcase, decode_params(val[3]) ]
|
272
|
+
}
|
273
|
+
| TOKEN params opt_semicolon
|
274
|
+
{
|
275
|
+
[ val[0].downcase, nil, decode_params(val[1]) ]
|
276
|
+
}
|
277
|
+
|
278
|
+
params : /* none */
|
279
|
+
{
|
280
|
+
{}
|
281
|
+
}
|
282
|
+
| params ';' TOKEN '=' value
|
283
|
+
{
|
284
|
+
val[0][ val[2].downcase ] = val[4]
|
285
|
+
val[0]
|
286
|
+
}
|
287
|
+
|
288
|
+
value : TOKEN
|
289
|
+
| QUOTED
|
290
|
+
|
291
|
+
cencode : TOKEN
|
292
|
+
{
|
293
|
+
val[0].downcase
|
294
|
+
}
|
295
|
+
|
296
|
+
cdisp : TOKEN params opt_semicolon
|
297
|
+
{
|
298
|
+
[ val[0].downcase, decode_params(val[1]) ]
|
299
|
+
}
|
300
|
+
|
301
|
+
opt_semicolon
|
302
|
+
:
|
303
|
+
| ';'
|
304
|
+
|
305
|
+
atom : ATOM
|
306
|
+
| FROM
|
307
|
+
| BY
|
308
|
+
| VIA
|
309
|
+
| WITH
|
310
|
+
| ID
|
311
|
+
| FOR
|
312
|
+
|
313
|
+
end
|
314
|
+
|
315
|
+
|
316
|
+
---- header
|
317
|
+
#
|
318
|
+
# parser.rb
|
319
|
+
#
|
320
|
+
# Copyright (c) 1998-2004 Minero Aoki
|
321
|
+
#
|
322
|
+
# This program is free software.
|
323
|
+
# You can distribute/modify this program under the terms of
|
324
|
+
# the GNU Lesser General Public License version 2.1.
|
325
|
+
#
|
326
|
+
|
327
|
+
require 'tmail/scanner'
|
328
|
+
require 'tmail/textutils'
|
329
|
+
|
330
|
+
---- inner
|
331
|
+
|
332
|
+
include TextUtils
|
333
|
+
|
334
|
+
def self.parse( ident, str, cmt = nil )
|
335
|
+
new.parse(ident, str, cmt)
|
336
|
+
end
|
337
|
+
|
338
|
+
MAILP_DEBUG = false
|
339
|
+
|
340
|
+
def initialize
|
341
|
+
self.debug = MAILP_DEBUG
|
342
|
+
end
|
343
|
+
|
344
|
+
def debug=( flag )
|
345
|
+
@yydebug = flag && Racc_debug_parser
|
346
|
+
@scanner_debug = flag
|
347
|
+
end
|
348
|
+
|
349
|
+
def debug
|
350
|
+
@yydebug
|
351
|
+
end
|
352
|
+
|
353
|
+
def parse( ident, str, comments = nil )
|
354
|
+
@scanner = Scanner.new(str, ident, comments)
|
355
|
+
@scanner.debug = @scanner_debug
|
356
|
+
@first = [ident, ident]
|
357
|
+
result = yyparse(self, :parse_in)
|
358
|
+
comments.map! {|c| to_kcode(c) } if comments
|
359
|
+
result
|
360
|
+
end
|
361
|
+
|
362
|
+
private
|
363
|
+
|
364
|
+
def parse_in( &block )
|
365
|
+
yield @first
|
366
|
+
@scanner.scan(&block)
|
367
|
+
end
|
368
|
+
|
369
|
+
def on_error( t, val, vstack )
|
370
|
+
raise SyntaxError, "parse error on token #{racc_token2str t}"
|
371
|
+
end
|
372
|
+
|
@@ -0,0 +1,356 @@
|
|
1
|
+
#
|
2
|
+
# port.rb
|
3
|
+
#
|
4
|
+
# Copyright (c) 1998-2004 Minero Aoki
|
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.1.
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'tmail/stringio'
|
12
|
+
|
13
|
+
module TMail
|
14
|
+
|
15
|
+
class Port
|
16
|
+
def reproducible?
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
###
|
23
|
+
### FilePort
|
24
|
+
###
|
25
|
+
|
26
|
+
class FilePort < Port
|
27
|
+
|
28
|
+
def initialize(fname)
|
29
|
+
@path = File.expand_path(fname)
|
30
|
+
super()
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :path
|
34
|
+
alias filename path
|
35
|
+
|
36
|
+
alias ident path
|
37
|
+
|
38
|
+
def ==(other)
|
39
|
+
other.respond_to?(:path) and @path == other.path
|
40
|
+
end
|
41
|
+
|
42
|
+
alias eql? ==
|
43
|
+
|
44
|
+
def hash
|
45
|
+
@path.hash
|
46
|
+
end
|
47
|
+
|
48
|
+
def inspect
|
49
|
+
"#<#{self.class} #{@path}>"
|
50
|
+
end
|
51
|
+
|
52
|
+
def reproducible?
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
def size
|
57
|
+
File.size(@path)
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def ropen(&block)
|
62
|
+
File.open(@path, &block)
|
63
|
+
end
|
64
|
+
|
65
|
+
def wopen(&block)
|
66
|
+
File.open(@path, 'w', &block)
|
67
|
+
end
|
68
|
+
|
69
|
+
def aopen(&block)
|
70
|
+
File.open(@path, '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 @path
|
83
|
+
end
|
84
|
+
|
85
|
+
def move_to(port)
|
86
|
+
begin
|
87
|
+
File.link @path, port.path
|
88
|
+
rescue Errno::EXDEV
|
89
|
+
copy_to port
|
90
|
+
end
|
91
|
+
File.unlink @path
|
92
|
+
end
|
93
|
+
|
94
|
+
alias mv move_to
|
95
|
+
|
96
|
+
def copy_to(port)
|
97
|
+
if port.is_a?(FilePort)
|
98
|
+
copy_file @path, port.path
|
99
|
+
else
|
100
|
+
File.open(@path) {|r|
|
101
|
+
port.wopen {|w|
|
102
|
+
while s = r.sysread(4096)
|
103
|
+
w.write << s
|
104
|
+
end
|
105
|
+
}
|
106
|
+
}
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
alias cp copy_to
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def copy_file(src, dest)
|
115
|
+
File.open(src, 'rb') {|r|
|
116
|
+
File.open(dest, 'wb') {|w|
|
117
|
+
while str = r.read(2048)
|
118
|
+
w.write str
|
119
|
+
end
|
120
|
+
}
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
module MailFlags
|
128
|
+
|
129
|
+
def seen=(b)
|
130
|
+
set_status 'S', b
|
131
|
+
end
|
132
|
+
|
133
|
+
def seen?
|
134
|
+
get_status 'S'
|
135
|
+
end
|
136
|
+
|
137
|
+
def replied=(b)
|
138
|
+
set_status 'R', b
|
139
|
+
end
|
140
|
+
|
141
|
+
def replied?
|
142
|
+
get_status 'R'
|
143
|
+
end
|
144
|
+
|
145
|
+
def flagged=(b)
|
146
|
+
set_status 'F', b
|
147
|
+
end
|
148
|
+
|
149
|
+
def flagged?
|
150
|
+
get_status 'F'
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
def procinfostr(str, tag, true_p)
|
156
|
+
a = str.upcase.split(//)
|
157
|
+
a.push true_p ? tag : nil
|
158
|
+
a.delete tag unless true_p
|
159
|
+
a.compact.sort.join('').squeeze
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
class MhPort < FilePort
|
166
|
+
|
167
|
+
include MailFlags
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
def set_status(tag, flag)
|
172
|
+
begin
|
173
|
+
tmpfile = "#{@path}.tmailtmp.#{$$}"
|
174
|
+
File.open(tmpfile, 'w') {|f|
|
175
|
+
write_status f, tag, flag
|
176
|
+
}
|
177
|
+
File.unlink @path
|
178
|
+
File.link tmpfile, @path
|
179
|
+
ensure
|
180
|
+
File.unlink tmpfile
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def write_status(f, tag, flag)
|
185
|
+
stat = ''
|
186
|
+
File.open(@path) {|r|
|
187
|
+
while line = r.gets
|
188
|
+
if line.strip.empty?
|
189
|
+
break
|
190
|
+
elsif m = /\AX-TMail-Status:/i.match(line)
|
191
|
+
stat = m.post_match.strip
|
192
|
+
else
|
193
|
+
f.print line
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
s = procinfostr(stat, tag, flag)
|
198
|
+
f.puts 'X-TMail-Status: ' + s unless s.empty?
|
199
|
+
f.puts
|
200
|
+
|
201
|
+
while s = r.read(2048)
|
202
|
+
f.write s
|
203
|
+
end
|
204
|
+
}
|
205
|
+
end
|
206
|
+
|
207
|
+
def get_status(tag)
|
208
|
+
File.foreach(@path) {|line|
|
209
|
+
return false if line.strip.empty?
|
210
|
+
if m = /\AX-TMail-Status:/i.match(line)
|
211
|
+
return m.post_match.strip.include?(tag[0])
|
212
|
+
end
|
213
|
+
}
|
214
|
+
false
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
class MaildirPort < FilePort
|
221
|
+
|
222
|
+
def move_to_new
|
223
|
+
new = replace_dir(@path, 'new')
|
224
|
+
File.rename @path, new
|
225
|
+
@path = new
|
226
|
+
end
|
227
|
+
|
228
|
+
def move_to_cur
|
229
|
+
new = replace_dir(@path, 'cur')
|
230
|
+
File.rename @path, new
|
231
|
+
@path = new
|
232
|
+
end
|
233
|
+
|
234
|
+
def replace_dir(path, dir)
|
235
|
+
"#{File.dirname File.dirname(path)}/#{dir}/#{File.basename path}"
|
236
|
+
end
|
237
|
+
private :replace_dir
|
238
|
+
|
239
|
+
|
240
|
+
include MailFlags
|
241
|
+
|
242
|
+
private
|
243
|
+
|
244
|
+
MAIL_FILE = /\A(\d+\.[\d_]+\.[^:]+)(?:\:(\d),(\w+)?)?\z/
|
245
|
+
|
246
|
+
def set_status(tag, flag)
|
247
|
+
if m = MAIL_FILE.match(File.basename(@path))
|
248
|
+
s, uniq, type, info, = m.to_a
|
249
|
+
return if type and type != '2' # do not change anything
|
250
|
+
newname = File.dirname(@path) + '/' +
|
251
|
+
uniq + ':2,' + procinfostr(info.to_s, tag, flag)
|
252
|
+
else
|
253
|
+
newname = @path + ':2,' + tag
|
254
|
+
end
|
255
|
+
|
256
|
+
File.link @path, newname
|
257
|
+
File.unlink @path
|
258
|
+
@path = newname
|
259
|
+
end
|
260
|
+
|
261
|
+
def get_status(tag)
|
262
|
+
m = MAIL_FILE.match(File.basename(@path)) or return false
|
263
|
+
m[2] == '2' and m[3].to_s.include?(tag[0])
|
264
|
+
end
|
265
|
+
|
266
|
+
end
|
267
|
+
|
268
|
+
|
269
|
+
###
|
270
|
+
### StringPort
|
271
|
+
###
|
272
|
+
|
273
|
+
class StringPort < Port
|
274
|
+
|
275
|
+
def initialize(str = '')
|
276
|
+
@buffer = str
|
277
|
+
super()
|
278
|
+
end
|
279
|
+
|
280
|
+
def string
|
281
|
+
@buffer
|
282
|
+
end
|
283
|
+
|
284
|
+
def to_s
|
285
|
+
@buffer.dup
|
286
|
+
end
|
287
|
+
|
288
|
+
alias read_all to_s
|
289
|
+
|
290
|
+
def size
|
291
|
+
@buffer.size
|
292
|
+
end
|
293
|
+
|
294
|
+
def ==(other)
|
295
|
+
other.is_a?(StringPort) and @buffer.equal?(other.string)
|
296
|
+
end
|
297
|
+
|
298
|
+
alias eql? ==
|
299
|
+
|
300
|
+
def hash
|
301
|
+
@buffer.object_id.hash
|
302
|
+
end
|
303
|
+
|
304
|
+
def inspect
|
305
|
+
"#<#{self.class}:id=#{sprintf '0x%x', @buffer.object_id}>"
|
306
|
+
end
|
307
|
+
|
308
|
+
def reproducible?
|
309
|
+
true
|
310
|
+
end
|
311
|
+
|
312
|
+
def ropen(&block)
|
313
|
+
# FIXME: Should we raise ENOENT?
|
314
|
+
raise Errno::ENOENT, "#{inspect} is already removed" unless @buffer
|
315
|
+
StringInput.open(@buffer, &block)
|
316
|
+
end
|
317
|
+
|
318
|
+
def wopen(&block)
|
319
|
+
@buffer = ''
|
320
|
+
StringOutput.new(@buffer, &block)
|
321
|
+
end
|
322
|
+
|
323
|
+
def aopen(&block)
|
324
|
+
@buffer ||= ''
|
325
|
+
StringOutput.new(@buffer, &block)
|
326
|
+
end
|
327
|
+
|
328
|
+
def remove
|
329
|
+
@buffer = nil
|
330
|
+
end
|
331
|
+
|
332
|
+
alias rm remove
|
333
|
+
|
334
|
+
def copy_to(port)
|
335
|
+
port.wopen {|f|
|
336
|
+
f.write @buffer
|
337
|
+
}
|
338
|
+
end
|
339
|
+
|
340
|
+
alias cp copy_to
|
341
|
+
|
342
|
+
def move_to(port)
|
343
|
+
if port.is_a?(StringPort)
|
344
|
+
tmp = @buffer
|
345
|
+
port.instance_eval {
|
346
|
+
@buffer = tmp
|
347
|
+
}
|
348
|
+
else
|
349
|
+
copy_to port
|
350
|
+
end
|
351
|
+
remove
|
352
|
+
end
|
353
|
+
|
354
|
+
end
|
355
|
+
|
356
|
+
end # module TMail
|