Added 'list_only' option (and modified 'run()' to respect it).
[python/dscho.git] / Lib / mailbox.py
blob3c758ff6e12d12e177e06ab2145a1b6a9e5974e3
1 #! /usr/bin/env python
3 """Classes to handle Unix style, MMDF style, and MH style mailboxes."""
6 import rfc822
7 import os
9 class _Mailbox:
11 def __init__(self, fp):
12 self.fp = fp
13 self.seekp = 0
15 def seek(self, pos, whence=0):
16 if whence==1: # Relative to current position
17 self.pos = self.pos + pos
18 if whence==2: # Relative to file's end
19 self.pos = self.stop + pos
20 else: # Default - absolute position
21 self.pos = self.start + pos
23 def next(self):
24 while 1:
25 self.fp.seek(self.seekp)
26 try:
27 self._search_start()
28 except EOFError:
29 self.seekp = self.fp.tell()
30 return None
31 start = self.fp.tell()
32 self._search_end()
33 self.seekp = stop = self.fp.tell()
34 if start <> stop:
35 break
36 return rfc822.Message(_Subfile(self.fp, start, stop))
38 class _Subfile:
40 def __init__(self, fp, start, stop):
41 self.fp = fp
42 self.start = start
43 self.stop = stop
44 self.pos = self.start
46 def read(self, length = None):
47 if self.pos >= self.stop:
48 return ''
49 remaining = self.stop - self.pos
50 if length is None or length < 0:
51 length = remaining
52 elif length > remaining:
53 length = remaining
54 self.fp.seek(self.pos)
55 data = self.fp.read(length)
56 self.pos = self.fp.tell()
57 return data
59 def readline(self, length = None):
60 if self.pos >= self.stop:
61 return ''
62 if length is None:
63 length = self.stop - self.pos
64 self.fp.seek(self.pos)
65 data = self.fp.readline(length)
66 self.pos = self.fp.tell()
67 return data
69 def readlines(self, sizehint = -1):
70 lines = []
71 while 1:
72 line = self.readline()
73 if not line:
74 break
75 lines.append(line)
76 if sizehint >= 0:
77 sizehint = sizehint - len(line)
78 if sizehint <= 0:
79 break
80 return lines
82 def tell(self):
83 return self.pos - self.start
85 def seek(self, pos, whence=0):
86 if whence == 0:
87 self.pos = self.start + pos
88 elif whence == 1:
89 self.pos = self.pos + pos
90 elif whence == 2:
91 self.pos = self.stop + pos
93 def close(self):
94 del self.fp
96 class UnixMailbox(_Mailbox):
98 def _search_start(self):
99 while 1:
100 line = self.fp.readline()
101 if not line:
102 raise EOFError
103 if line[:5] == 'From ' and self._isrealfromline(line):
104 return
106 def _search_end(self):
107 while 1:
108 pos = self.fp.tell()
109 line = self.fp.readline()
110 if not line:
111 return
112 if line[:5] == 'From ' and self._isrealfromline(line):
113 self.fp.seek(pos)
114 return
116 # An overridable mechanism to test for From-line-ness.
117 # You can either specify a different regular expression
118 # or define a whole new _isrealfromline() method.
119 # Note that this only gets called for lines starting with
120 # the 5 characters "From ".
122 _fromlinepattern = r"From \s*[^\s]+\s+\w\w\w\s+\w\w\w\s+\d?\d\s+" \
123 r"\d?\d:\d\d(:\d\d)?(\s+[^\s]+)?\s+\d\d\d\d\s*$"
124 _regexp = None
126 def _isrealfromline(self, line):
127 if not self._regexp:
128 import re
129 self._regexp = re.compile(self._fromlinepattern)
130 return self._regexp.match(line)
132 class MmdfMailbox(_Mailbox):
134 def _search_start(self):
135 while 1:
136 line = self.fp.readline()
137 if not line:
138 raise EOFError
139 if line[:5] == '\001\001\001\001\n':
140 return
142 def _search_end(self):
143 while 1:
144 pos = self.fp.tell()
145 line = self.fp.readline()
146 if not line:
147 return
148 if line == '\001\001\001\001\n':
149 self.fp.seek(pos)
150 return
152 class MHMailbox:
154 def __init__(self, dirname):
155 import re
156 pat = re.compile('^[0-9][0-9]*$')
157 self.dirname = dirname
158 files = os.listdir(self.dirname)
159 self.boxes = []
160 for f in files:
161 if pat.match(f):
162 self.boxes.append(f)
164 def next(self):
165 if not self.boxes:
166 return None
167 fn = self.boxes[0]
168 del self.boxes[0]
169 fp = open(os.path.join(self.dirname, fn))
170 return rfc822.Message(fp)
172 class Maildir:
174 # Qmail directory mailbox
176 def __init__(self, dirname):
177 import string
178 self.dirname = dirname
179 self.boxes = []
181 # check for new mail
182 newdir = os.path.join(self.dirname, 'new')
183 for file in os.listdir(newdir):
184 if len(string.split(file, '.')) > 2:
185 self.boxes.append(os.path.join(newdir, file))
187 # Now check for current mail in this maildir
188 curdir = os.path.join(self.dirname, 'cur')
189 for file in os.listdir(curdir):
190 if len(string.split(file, '.')) > 2:
191 self.boxes.append(os.path.join(curdir, file))
193 def next(self):
194 if not self.boxes:
195 return None
196 fn = self.boxes[0]
197 del self.boxes[0]
198 fp = open(os.path.join(self.dirname, fn))
199 return rfc822.Message(fp)
201 class BabylMailbox(_Mailbox):
203 def _search_start(self):
204 while 1:
205 line = self.fp.readline()
206 if not line:
207 raise EOFError
208 if line == '*** EOOH ***\n':
209 return
211 def _search_end(self):
212 while 1:
213 pos = self.fp.tell()
214 line = self.fp.readline()
215 if not line:
216 return
217 if line == '\037\014\n':
218 self.fp.seek(pos)
219 return
222 def _test():
223 import time
224 import sys
225 import string
226 import os
228 args = sys.argv[1:]
229 if not args:
230 for key in 'MAILDIR', 'MAIL', 'LOGNAME', 'USER':
231 if os.environ.has_key(key):
232 mbox = os.environ[key]
233 break
234 else:
235 print "$MAIL, $LOGNAME nor $USER set -- who are you?"
236 return
237 else:
238 mbox = args[0]
239 if mbox[:1] == '+':
240 mbox = os.environ['HOME'] + '/Mail/' + mbox[1:]
241 elif not '/' in mbox:
242 mbox = '/usr/mail/' + mbox
243 if os.path.isdir(mbox):
244 if os.path.isdir(os.path.join(mbox, 'cur')):
245 mb = Maildir(mbox)
246 else:
247 mb = MHMailbox(mbox)
248 else:
249 fp = open(mbox, 'r')
250 mb = UnixMailbox(fp)
252 msgs = []
253 while 1:
254 msg = mb.next()
255 if msg is None:
256 break
257 msgs.append(msg)
258 msg.fp = None
259 if len(args) > 1:
260 num = string.atoi(args[1])
261 print 'Message %d body:'%num
262 msg = msgs[num-1]
263 msg.rewindbody()
264 sys.stdout.write(msg.fp.read())
265 else:
266 print 'Mailbox',mbox,'has',len(msgs),'messages:'
267 for msg in msgs:
268 f = msg.getheader('from') or ""
269 s = msg.getheader('subject') or ""
270 d = msg.getheader('date') or ""
271 print '%20.20s %18.18s %-30.30s'%(f, d[5:], s)
274 if __name__ == '__main__':
275 _test()