blob: 6cf012830716f760447f8f137011a0c6b4ced517 [file] [log] [blame]
Guido van Rossum54f22ed2000-02-04 15:10:34 +00001"""MH interface -- purely object-oriented (well, almost)
2
3Executive summary:
4
5import mhlib
6
7mh = mhlib.MH() # use default mailbox directory and profile
8mh = mhlib.MH(mailbox) # override mailbox location (default from profile)
9mh = mhlib.MH(mailbox, profile) # override mailbox and profile
10
11mh.error(format, ...) # print error message -- can be overridden
12s = mh.getprofile(key) # profile entry (None if not set)
13path = mh.getpath() # mailbox pathname
14name = mh.getcontext() # name of current folder
15mh.setcontext(name) # set name of current folder
16
17list = mh.listfolders() # names of top-level folders
18list = mh.listallfolders() # names of all folders, including subfolders
19list = mh.listsubfolders(name) # direct subfolders of given folder
20list = mh.listallsubfolders(name) # all subfolders of given folder
21
22mh.makefolder(name) # create new folder
23mh.deletefolder(name) # delete folder -- must have no subfolders
24
25f = mh.openfolder(name) # new open folder object
26
27f.error(format, ...) # same as mh.error(format, ...)
28path = f.getfullname() # folder's full pathname
29path = f.getsequencesfilename() # full pathname of folder's sequences file
30path = f.getmessagefilename(n) # full pathname of message n in folder
31
32list = f.listmessages() # list of messages in folder (as numbers)
33n = f.getcurrent() # get current message
34f.setcurrent(n) # set current message
35list = f.parsesequence(seq) # parse msgs syntax into list of messages
36n = f.getlast() # get last message (0 if no messagse)
37f.setlast(n) # set last message (internal use only)
38
39dict = f.getsequences() # dictionary of sequences in folder {name: list}
40f.putsequences(dict) # write sequences back to folder
41
42f.createmessage(n, fp) # add message from file f as number n
43f.removemessages(list) # remove messages in list from folder
44f.refilemessages(list, tofolder) # move messages in list to other folder
45f.movemessage(n, tofolder, ton) # move one message to a given destination
46f.copymessage(n, tofolder, ton) # copy one message to a given destination
47
48m = f.openmessage(n) # new open message object (costs a file descriptor)
49m is a derived class of mimetools.Message(rfc822.Message), with:
50s = m.getheadertext() # text of message's headers
51s = m.getheadertext(pred) # text of message's headers, filtered by pred
52s = m.getbodytext() # text of message's body, decoded
53s = m.getbodytext(0) # text of message's body, not decoded
54"""
55
Guido van Rossum56013131994-06-23 12:06:02 +000056# XXX To do, functionality:
Guido van Rossum56013131994-06-23 12:06:02 +000057# - annotate messages
Guido van Rossum4fe6caa1999-02-24 16:25:17 +000058# - send messages
Guido van Rossum56013131994-06-23 12:06:02 +000059#
Guido van Rossum40b2cfb1995-01-02 18:38:23 +000060# XXX To do, organization:
Guido van Rossum56013131994-06-23 12:06:02 +000061# - move IntSet to separate file
62# - move most Message functionality to module mimetools
63
64
65# Customizable defaults
66
67MH_PROFILE = '~/.mh_profile'
68PATH = '~/Mail'
69MH_SEQUENCES = '.mh_sequences'
70FOLDER_PROTECT = 0700
71
72
73# Imported modules
74
75import os
Guido van Rossum508a0921996-05-28 22:59:37 +000076import sys
Guido van Rossum56013131994-06-23 12:06:02 +000077from stat import ST_NLINK
Guido van Rossum9694fca1997-10-22 21:00:49 +000078import re
Guido van Rossum56013131994-06-23 12:06:02 +000079import mimetools
80import multifile
Guido van Rossum40b2cfb1995-01-02 18:38:23 +000081import shutil
Guido van Rossum7cfd31e1997-04-16 02:45:08 +000082from bisect import bisect
Guido van Rossum56013131994-06-23 12:06:02 +000083
Skip Montanaro17ab1232001-01-24 06:27:27 +000084__all__ = ["MH","Error","Folder","Message"]
Guido van Rossum56013131994-06-23 12:06:02 +000085
86# Exported constants
87
Fred Drakeffdc48f2000-06-29 05:06:02 +000088class Error(Exception):
89 pass
Guido van Rossum56013131994-06-23 12:06:02 +000090
91
Guido van Rossum56013131994-06-23 12:06:02 +000092class MH:
Guido van Rossum54f22ed2000-02-04 15:10:34 +000093 """Class representing a particular collection of folders.
94 Optional constructor arguments are the pathname for the directory
95 containing the collection, and the MH profile to use.
96 If either is omitted or empty a default is used; the default
97 directory is taken from the MH profile if it is specified there."""
Guido van Rossum56013131994-06-23 12:06:02 +000098
Guido van Rossum0c5e0491997-04-16 02:47:12 +000099 def __init__(self, path = None, profile = None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000100 """Constructor."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000101 if not profile: profile = MH_PROFILE
102 self.profile = os.path.expanduser(profile)
103 if not path: path = self.getprofile('Path')
104 if not path: path = PATH
105 if not os.path.isabs(path) and path[0] != '~':
106 path = os.path.join('~', path)
107 path = os.path.expanduser(path)
108 if not os.path.isdir(path): raise Error, 'MH() path not found'
109 self.path = path
Guido van Rossum56013131994-06-23 12:06:02 +0000110
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000111 def __repr__(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000112 """String representation."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000113 return 'MH(%s, %s)' % (`self.path`, `self.profile`)
Guido van Rossum56013131994-06-23 12:06:02 +0000114
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000115 def error(self, msg, *args):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000116 """Routine to print an error. May be overridden by a derived class."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000117 sys.stderr.write('MH error: %s\n' % (msg % args))
Guido van Rossum56013131994-06-23 12:06:02 +0000118
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000119 def getprofile(self, key):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000120 """Return a profile entry, None if not found."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000121 return pickline(self.profile, key)
Guido van Rossum56013131994-06-23 12:06:02 +0000122
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000123 def getpath(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000124 """Return the path (the name of the collection's directory)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000125 return self.path
Guido van Rossum56013131994-06-23 12:06:02 +0000126
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000127 def getcontext(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000128 """Return the name of the current folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000129 context = pickline(os.path.join(self.getpath(), 'context'),
130 'Current-Folder')
131 if not context: context = 'inbox'
132 return context
Guido van Rossum56013131994-06-23 12:06:02 +0000133
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000134 def setcontext(self, context):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000135 """Set the name of the current folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000136 fn = os.path.join(self.getpath(), 'context')
137 f = open(fn, "w")
138 f.write("Current-Folder: %s\n" % context)
139 f.close()
Guido van Rossum508a0921996-05-28 22:59:37 +0000140
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000141 def listfolders(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000142 """Return the names of the top-level folders."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000143 folders = []
144 path = self.getpath()
145 for name in os.listdir(path):
146 fullname = os.path.join(path, name)
147 if os.path.isdir(fullname):
148 folders.append(name)
149 folders.sort()
150 return folders
Guido van Rossum56013131994-06-23 12:06:02 +0000151
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000152 def listsubfolders(self, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000153 """Return the names of the subfolders in a given folder
154 (prefixed with the given folder name)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000155 fullname = os.path.join(self.path, name)
156 # Get the link count so we can avoid listing folders
157 # that have no subfolders.
158 st = os.stat(fullname)
159 nlinks = st[ST_NLINK]
160 if nlinks <= 2:
161 return []
162 subfolders = []
163 subnames = os.listdir(fullname)
164 for subname in subnames:
165 fullsubname = os.path.join(fullname, subname)
166 if os.path.isdir(fullsubname):
167 name_subname = os.path.join(name, subname)
168 subfolders.append(name_subname)
169 # Stop looking for subfolders when
170 # we've seen them all
171 nlinks = nlinks - 1
172 if nlinks <= 2:
173 break
174 subfolders.sort()
175 return subfolders
Guido van Rossum56013131994-06-23 12:06:02 +0000176
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000177 def listallfolders(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000178 """Return the names of all folders and subfolders, recursively."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000179 return self.listallsubfolders('')
Guido van Rossum56013131994-06-23 12:06:02 +0000180
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000181 def listallsubfolders(self, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000182 """Return the names of subfolders in a given folder, recursively."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000183 fullname = os.path.join(self.path, name)
184 # Get the link count so we can avoid listing folders
185 # that have no subfolders.
186 st = os.stat(fullname)
187 nlinks = st[ST_NLINK]
188 if nlinks <= 2:
189 return []
190 subfolders = []
191 subnames = os.listdir(fullname)
192 for subname in subnames:
193 if subname[0] == ',' or isnumeric(subname): continue
194 fullsubname = os.path.join(fullname, subname)
195 if os.path.isdir(fullsubname):
196 name_subname = os.path.join(name, subname)
197 subfolders.append(name_subname)
198 if not os.path.islink(fullsubname):
199 subsubfolders = self.listallsubfolders(
200 name_subname)
201 subfolders = subfolders + subsubfolders
202 # Stop looking for subfolders when
203 # we've seen them all
204 nlinks = nlinks - 1
205 if nlinks <= 2:
206 break
207 subfolders.sort()
208 return subfolders
Guido van Rossum56013131994-06-23 12:06:02 +0000209
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000210 def openfolder(self, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000211 """Return a new Folder object for the named folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000212 return Folder(self, name)
Guido van Rossum56013131994-06-23 12:06:02 +0000213
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000214 def makefolder(self, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000215 """Create a new folder (or raise os.error if it cannot be created)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000216 protect = pickline(self.profile, 'Folder-Protect')
217 if protect and isnumeric(protect):
Eric S. Raymond66d99192001-02-09 09:19:27 +0000218 mode = int(protect, 8)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000219 else:
220 mode = FOLDER_PROTECT
221 os.mkdir(os.path.join(self.getpath(), name), mode)
Guido van Rossum56013131994-06-23 12:06:02 +0000222
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000223 def deletefolder(self, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000224 """Delete a folder. This removes files in the folder but not
225 subdirectories. Raise os.error if deleting the folder itself fails."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000226 fullname = os.path.join(self.getpath(), name)
227 for subname in os.listdir(fullname):
228 fullsubname = os.path.join(fullname, subname)
229 try:
230 os.unlink(fullsubname)
231 except os.error:
232 self.error('%s not deleted, continuing...' %
233 fullsubname)
234 os.rmdir(fullname)
Guido van Rossum56013131994-06-23 12:06:02 +0000235
236
Guido van Rossum9694fca1997-10-22 21:00:49 +0000237numericprog = re.compile('^[1-9][0-9]*$')
Guido van Rossum56013131994-06-23 12:06:02 +0000238def isnumeric(str):
Guido van Rossum9694fca1997-10-22 21:00:49 +0000239 return numericprog.match(str) is not None
Guido van Rossum56013131994-06-23 12:06:02 +0000240
241class Folder:
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000242 """Class representing a particular folder."""
Guido van Rossum56013131994-06-23 12:06:02 +0000243
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000244 def __init__(self, mh, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000245 """Constructor."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000246 self.mh = mh
247 self.name = name
248 if not os.path.isdir(self.getfullname()):
249 raise Error, 'no folder %s' % name
Guido van Rossum56013131994-06-23 12:06:02 +0000250
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000251 def __repr__(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000252 """String representation."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000253 return 'Folder(%s, %s)' % (`self.mh`, `self.name`)
Guido van Rossum56013131994-06-23 12:06:02 +0000254
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000255 def error(self, *args):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000256 """Error message handler."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000257 apply(self.mh.error, args)
Guido van Rossum56013131994-06-23 12:06:02 +0000258
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000259 def getfullname(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000260 """Return the full pathname of the folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000261 return os.path.join(self.mh.path, self.name)
Guido van Rossum56013131994-06-23 12:06:02 +0000262
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000263 def getsequencesfilename(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000264 """Return the full pathname of the folder's sequences file."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000265 return os.path.join(self.getfullname(), MH_SEQUENCES)
Guido van Rossum56013131994-06-23 12:06:02 +0000266
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000267 def getmessagefilename(self, n):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000268 """Return the full pathname of a message in the folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000269 return os.path.join(self.getfullname(), str(n))
Guido van Rossum56013131994-06-23 12:06:02 +0000270
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000271 def listsubfolders(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000272 """Return list of direct subfolders."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000273 return self.mh.listsubfolders(self.name)
Guido van Rossum56013131994-06-23 12:06:02 +0000274
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000275 def listallsubfolders(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000276 """Return list of all subfolders."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000277 return self.mh.listallsubfolders(self.name)
Guido van Rossum56013131994-06-23 12:06:02 +0000278
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000279 def listmessages(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000280 """Return the list of messages currently present in the folder.
281 As a side effect, set self.last to the last message (or 0)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000282 messages = []
283 match = numericprog.match
284 append = messages.append
285 for name in os.listdir(self.getfullname()):
Guido van Rossumd9d26251998-06-23 14:43:06 +0000286 if match(name):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000287 append(name)
Eric S. Raymond66d99192001-02-09 09:19:27 +0000288 messages = map(int, messages)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000289 messages.sort()
290 if messages:
291 self.last = messages[-1]
292 else:
293 self.last = 0
294 return messages
Guido van Rossum56013131994-06-23 12:06:02 +0000295
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000296 def getsequences(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000297 """Return the set of sequences for the folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000298 sequences = {}
299 fullname = self.getsequencesfilename()
300 try:
301 f = open(fullname, 'r')
302 except IOError:
303 return sequences
304 while 1:
305 line = f.readline()
306 if not line: break
Eric S. Raymond66d99192001-02-09 09:19:27 +0000307 fields = line.split(':')
Fred Drake8152d322000-12-12 23:20:45 +0000308 if len(fields) != 2:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000309 self.error('bad sequence in %s: %s' %
Eric S. Raymond66d99192001-02-09 09:19:27 +0000310 (fullname, line.strip()))
311 key = fields[0].strip()
312 value = IntSet(fields[1].strip(), ' ').tolist()
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000313 sequences[key] = value
314 return sequences
Guido van Rossum56013131994-06-23 12:06:02 +0000315
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000316 def putsequences(self, sequences):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000317 """Write the set of sequences back to the folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000318 fullname = self.getsequencesfilename()
319 f = None
320 for key in sequences.keys():
321 s = IntSet('', ' ')
322 s.fromlist(sequences[key])
323 if not f: f = open(fullname, 'w')
324 f.write('%s: %s\n' % (key, s.tostring()))
325 if not f:
326 try:
327 os.unlink(fullname)
328 except os.error:
329 pass
330 else:
331 f.close()
Guido van Rossum56013131994-06-23 12:06:02 +0000332
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000333 def getcurrent(self):
Fred Drakeffdc48f2000-06-29 05:06:02 +0000334 """Return the current message. Raise Error when there is none."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000335 seqs = self.getsequences()
336 try:
337 return max(seqs['cur'])
338 except (ValueError, KeyError):
339 raise Error, "no cur message"
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000340
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000341 def setcurrent(self, n):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000342 """Set the current message."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000343 updateline(self.getsequencesfilename(), 'cur', str(n), 0)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000344
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000345 def parsesequence(self, seq):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000346 """Parse an MH sequence specification into a message list.
347 Attempt to mimic mh-sequence(5) as close as possible.
348 Also attempt to mimic observed behavior regarding which
349 conditions cause which error messages."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000350 # XXX Still not complete (see mh-format(5)).
351 # Missing are:
352 # - 'prev', 'next' as count
353 # - Sequence-Negation option
354 all = self.listmessages()
355 # Observed behavior: test for empty folder is done first
356 if not all:
357 raise Error, "no messages in %s" % self.name
358 # Common case first: all is frequently the default
359 if seq == 'all':
360 return all
361 # Test for X:Y before X-Y because 'seq:-n' matches both
Eric S. Raymond66d99192001-02-09 09:19:27 +0000362 i = seq.find(':')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000363 if i >= 0:
364 head, dir, tail = seq[:i], '', seq[i+1:]
365 if tail[:1] in '-+':
366 dir, tail = tail[:1], tail[1:]
367 if not isnumeric(tail):
368 raise Error, "bad message list %s" % seq
369 try:
Eric S. Raymond66d99192001-02-09 09:19:27 +0000370 count = int(tail)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000371 except (ValueError, OverflowError):
372 # Can't use sys.maxint because of i+count below
373 count = len(all)
374 try:
375 anchor = self._parseindex(head, all)
376 except Error, msg:
377 seqs = self.getsequences()
378 if not seqs.has_key(head):
379 if not msg:
380 msg = "bad message list %s" % seq
381 raise Error, msg, sys.exc_info()[2]
382 msgs = seqs[head]
383 if not msgs:
384 raise Error, "sequence %s empty" % head
385 if dir == '-':
386 return msgs[-count:]
387 else:
388 return msgs[:count]
389 else:
390 if not dir:
391 if head in ('prev', 'last'):
392 dir = '-'
393 if dir == '-':
394 i = bisect(all, anchor)
395 return all[max(0, i-count):i]
396 else:
397 i = bisect(all, anchor-1)
398 return all[i:i+count]
399 # Test for X-Y next
Eric S. Raymond66d99192001-02-09 09:19:27 +0000400 i = seq.find('-')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000401 if i >= 0:
402 begin = self._parseindex(seq[:i], all)
403 end = self._parseindex(seq[i+1:], all)
404 i = bisect(all, begin-1)
405 j = bisect(all, end)
406 r = all[i:j]
407 if not r:
408 raise Error, "bad message list %s" % seq
409 return r
410 # Neither X:Y nor X-Y; must be a number or a (pseudo-)sequence
411 try:
412 n = self._parseindex(seq, all)
413 except Error, msg:
414 seqs = self.getsequences()
415 if not seqs.has_key(seq):
416 if not msg:
417 msg = "bad message list %s" % seq
418 raise Error, msg
419 return seqs[seq]
420 else:
421 if n not in all:
422 if isnumeric(seq):
423 raise Error, "message %d doesn't exist" % n
424 else:
425 raise Error, "no %s message" % seq
426 else:
427 return [n]
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000428
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000429 def _parseindex(self, seq, all):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000430 """Internal: parse a message number (or cur, first, etc.)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000431 if isnumeric(seq):
432 try:
Eric S. Raymond66d99192001-02-09 09:19:27 +0000433 return int(seq)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000434 except (OverflowError, ValueError):
435 return sys.maxint
436 if seq in ('cur', '.'):
437 return self.getcurrent()
438 if seq == 'first':
439 return all[0]
440 if seq == 'last':
441 return all[-1]
442 if seq == 'next':
443 n = self.getcurrent()
444 i = bisect(all, n)
445 try:
446 return all[i]
447 except IndexError:
448 raise Error, "no next message"
449 if seq == 'prev':
450 n = self.getcurrent()
451 i = bisect(all, n-1)
452 if i == 0:
453 raise Error, "no prev message"
454 try:
455 return all[i-1]
456 except IndexError:
457 raise Error, "no prev message"
458 raise Error, None
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000459
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000460 def openmessage(self, n):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000461 """Open a message -- returns a Message object."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000462 return Message(self, n)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000463
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000464 def removemessages(self, list):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000465 """Remove one or more messages -- may raise os.error."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000466 errors = []
467 deleted = []
468 for n in list:
469 path = self.getmessagefilename(n)
470 commapath = self.getmessagefilename(',' + str(n))
471 try:
472 os.unlink(commapath)
473 except os.error:
474 pass
475 try:
476 os.rename(path, commapath)
477 except os.error, msg:
478 errors.append(msg)
479 else:
480 deleted.append(n)
481 if deleted:
482 self.removefromallsequences(deleted)
483 if errors:
484 if len(errors) == 1:
485 raise os.error, errors[0]
486 else:
487 raise os.error, ('multiple errors:', errors)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000488
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000489 def refilemessages(self, list, tofolder, keepsequences=0):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000490 """Refile one or more messages -- may raise os.error.
491 'tofolder' is an open folder object."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000492 errors = []
493 refiled = {}
494 for n in list:
495 ton = tofolder.getlast() + 1
496 path = self.getmessagefilename(n)
497 topath = tofolder.getmessagefilename(ton)
498 try:
499 os.rename(path, topath)
500 except os.error:
501 # Try copying
502 try:
503 shutil.copy2(path, topath)
504 os.unlink(path)
505 except (IOError, os.error), msg:
506 errors.append(msg)
507 try:
508 os.unlink(topath)
509 except os.error:
510 pass
511 continue
512 tofolder.setlast(ton)
513 refiled[n] = ton
514 if refiled:
515 if keepsequences:
516 tofolder._copysequences(self, refiled.items())
517 self.removefromallsequences(refiled.keys())
518 if errors:
519 if len(errors) == 1:
520 raise os.error, errors[0]
521 else:
522 raise os.error, ('multiple errors:', errors)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000523
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000524 def _copysequences(self, fromfolder, refileditems):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000525 """Helper for refilemessages() to copy sequences."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000526 fromsequences = fromfolder.getsequences()
527 tosequences = self.getsequences()
528 changed = 0
529 for name, seq in fromsequences.items():
530 try:
531 toseq = tosequences[name]
532 new = 0
unknown3db163a2001-07-04 07:01:29 +0000533 except KeyError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000534 toseq = []
535 new = 1
536 for fromn, ton in refileditems:
537 if fromn in seq:
538 toseq.append(ton)
539 changed = 1
540 if new and toseq:
541 tosequences[name] = toseq
542 if changed:
543 self.putsequences(tosequences)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000544
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000545 def movemessage(self, n, tofolder, ton):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000546 """Move one message over a specific destination message,
547 which may or may not already exist."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000548 path = self.getmessagefilename(n)
549 # Open it to check that it exists
550 f = open(path)
551 f.close()
552 del f
553 topath = tofolder.getmessagefilename(ton)
554 backuptopath = tofolder.getmessagefilename(',%d' % ton)
555 try:
556 os.rename(topath, backuptopath)
557 except os.error:
558 pass
559 try:
560 os.rename(path, topath)
561 except os.error:
562 # Try copying
563 ok = 0
564 try:
565 tofolder.setlast(None)
566 shutil.copy2(path, topath)
567 ok = 1
568 finally:
569 if not ok:
570 try:
571 os.unlink(topath)
572 except os.error:
573 pass
574 os.unlink(path)
575 self.removefromallsequences([n])
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000576
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000577 def copymessage(self, n, tofolder, ton):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000578 """Copy one message over a specific destination message,
579 which may or may not already exist."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000580 path = self.getmessagefilename(n)
581 # Open it to check that it exists
582 f = open(path)
583 f.close()
584 del f
585 topath = tofolder.getmessagefilename(ton)
586 backuptopath = tofolder.getmessagefilename(',%d' % ton)
587 try:
588 os.rename(topath, backuptopath)
589 except os.error:
590 pass
591 ok = 0
592 try:
593 tofolder.setlast(None)
594 shutil.copy2(path, topath)
595 ok = 1
596 finally:
597 if not ok:
598 try:
599 os.unlink(topath)
600 except os.error:
601 pass
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000602
Guido van Rossum4e5cbcf1997-07-25 14:59:10 +0000603 def createmessage(self, n, txt):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000604 """Create a message, with text from the open file txt."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000605 path = self.getmessagefilename(n)
606 backuppath = self.getmessagefilename(',%d' % n)
607 try:
608 os.rename(path, backuppath)
609 except os.error:
610 pass
611 ok = 0
612 BUFSIZE = 16*1024
613 try:
614 f = open(path, "w")
615 while 1:
616 buf = txt.read(BUFSIZE)
617 if not buf:
618 break
619 f.write(buf)
620 f.close()
621 ok = 1
622 finally:
623 if not ok:
624 try:
625 os.unlink(path)
626 except os.error:
627 pass
Guido van Rossum4e5cbcf1997-07-25 14:59:10 +0000628
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000629 def removefromallsequences(self, list):
Thomas Wouters7e474022000-07-16 12:04:32 +0000630 """Remove one or more messages from all sequences (including last)
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000631 -- but not from 'cur'!!!"""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000632 if hasattr(self, 'last') and self.last in list:
633 del self.last
634 sequences = self.getsequences()
635 changed = 0
636 for name, seq in sequences.items():
637 if name == 'cur':
638 continue
639 for n in list:
640 if n in seq:
641 seq.remove(n)
642 changed = 1
643 if not seq:
644 del sequences[name]
645 if changed:
646 self.putsequences(sequences)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000647
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000648 def getlast(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000649 """Return the last message number."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000650 if not hasattr(self, 'last'):
Guido van Rossumf93befc2001-10-17 05:59:26 +0000651 self.listmessages() # Set self.last
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000652 return self.last
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000653
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000654 def setlast(self, last):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000655 """Set the last message number."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000656 if last is None:
657 if hasattr(self, 'last'):
658 del self.last
659 else:
660 self.last = last
Guido van Rossum56013131994-06-23 12:06:02 +0000661
662class Message(mimetools.Message):
663
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000664 def __init__(self, f, n, fp = None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000665 """Constructor."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000666 self.folder = f
667 self.number = n
668 if not fp:
669 path = f.getmessagefilename(n)
670 fp = open(path, 'r')
671 mimetools.Message.__init__(self, fp)
Guido van Rossum56013131994-06-23 12:06:02 +0000672
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000673 def __repr__(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000674 """String representation."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000675 return 'Message(%s, %s)' % (repr(self.folder), self.number)
Guido van Rossum56013131994-06-23 12:06:02 +0000676
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000677 def getheadertext(self, pred = None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000678 """Return the message's header text as a string. If an
679 argument is specified, it is used as a filter predicate to
680 decide which headers to return (its argument is the header
681 name converted to lower case)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000682 if not pred:
Eric S. Raymond66d99192001-02-09 09:19:27 +0000683 return ''.join(self.headers)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000684 headers = []
685 hit = 0
686 for line in self.headers:
Eric S. Raymond6e025bc2001-02-10 00:22:33 +0000687 if not line[0].isspace():
Eric S. Raymond66d99192001-02-09 09:19:27 +0000688 i = line.find(':')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000689 if i > 0:
Eric S. Raymond66d99192001-02-09 09:19:27 +0000690 hit = pred(line[:i].lower())
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000691 if hit: headers.append(line)
Eric S. Raymondc9838f92001-02-09 10:28:34 +0000692 return ''.join(headers)
Guido van Rossum56013131994-06-23 12:06:02 +0000693
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000694 def getbodytext(self, decode = 1):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000695 """Return the message's body text as string. This undoes a
696 Content-Transfer-Encoding, but does not interpret other MIME
697 features (e.g. multipart messages). To suppress decoding,
698 pass 0 as an argument."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000699 self.fp.seek(self.startofbody)
700 encoding = self.getencoding()
Guido van Rossum4fe6caa1999-02-24 16:25:17 +0000701 if not decode or encoding in ('', '7bit', '8bit', 'binary'):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000702 return self.fp.read()
703 from StringIO import StringIO
704 output = StringIO()
705 mimetools.decode(self.fp, output, encoding)
706 return output.getvalue()
Guido van Rossum56013131994-06-23 12:06:02 +0000707
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000708 def getbodyparts(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000709 """Only for multipart messages: return the message's body as a
710 list of SubMessage objects. Each submessage object behaves
711 (almost) as a Message object."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000712 if self.getmaintype() != 'multipart':
713 raise Error, 'Content-Type is not multipart/*'
714 bdry = self.getparam('boundary')
715 if not bdry:
716 raise Error, 'multipart/* without boundary param'
717 self.fp.seek(self.startofbody)
718 mf = multifile.MultiFile(self.fp)
719 mf.push(bdry)
720 parts = []
721 while mf.next():
722 n = str(self.number) + '.' + `1 + len(parts)`
723 part = SubMessage(self.folder, n, mf)
724 parts.append(part)
725 mf.pop()
726 return parts
Guido van Rossum56013131994-06-23 12:06:02 +0000727
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000728 def getbody(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000729 """Return body, either a string or a list of messages."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000730 if self.getmaintype() == 'multipart':
731 return self.getbodyparts()
732 else:
733 return self.getbodytext()
Guido van Rossum56013131994-06-23 12:06:02 +0000734
735
736class SubMessage(Message):
737
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000738 def __init__(self, f, n, fp):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000739 """Constructor."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000740 Message.__init__(self, f, n, fp)
741 if self.getmaintype() == 'multipart':
742 self.body = Message.getbodyparts(self)
743 else:
744 self.body = Message.getbodytext(self)
Guido van Rossum4fe6caa1999-02-24 16:25:17 +0000745 self.bodyencoded = Message.getbodytext(self, decode=0)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000746 # XXX If this is big, should remember file pointers
Guido van Rossum56013131994-06-23 12:06:02 +0000747
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000748 def __repr__(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000749 """String representation."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000750 f, n, fp = self.folder, self.number, self.fp
751 return 'SubMessage(%s, %s, %s)' % (f, n, fp)
Guido van Rossum56013131994-06-23 12:06:02 +0000752
Guido van Rossum4fe6caa1999-02-24 16:25:17 +0000753 def getbodytext(self, decode = 1):
754 if not decode:
755 return self.bodyencoded
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000756 if type(self.body) == type(''):
757 return self.body
Guido van Rossum56013131994-06-23 12:06:02 +0000758
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000759 def getbodyparts(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000760 if type(self.body) == type([]):
761 return self.body
Guido van Rossum56013131994-06-23 12:06:02 +0000762
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000763 def getbody(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000764 return self.body
Guido van Rossum56013131994-06-23 12:06:02 +0000765
766
Guido van Rossum56013131994-06-23 12:06:02 +0000767class IntSet:
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000768 """Class implementing sets of integers.
769
770 This is an efficient representation for sets consisting of several
771 continuous ranges, e.g. 1-100,200-400,402-1000 is represented
772 internally as a list of three pairs: [(1,100), (200,400),
773 (402,1000)]. The internal representation is always kept normalized.
774
775 The constructor has up to three arguments:
776 - the string used to initialize the set (default ''),
777 - the separator between ranges (default ',')
778 - the separator between begin and end of a range (default '-')
779 The separators must be strings (not regexprs) and should be different.
780
781 The tostring() function yields a string that can be passed to another
782 IntSet constructor; __repr__() is a valid IntSet constructor itself.
783 """
784
785 # XXX The default begin/end separator means that negative numbers are
786 # not supported very well.
787 #
788 # XXX There are currently no operations to remove set elements.
Guido van Rossum56013131994-06-23 12:06:02 +0000789
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000790 def __init__(self, data = None, sep = ',', rng = '-'):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000791 self.pairs = []
792 self.sep = sep
793 self.rng = rng
794 if data: self.fromstring(data)
Guido van Rossum56013131994-06-23 12:06:02 +0000795
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000796 def reset(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000797 self.pairs = []
Guido van Rossum56013131994-06-23 12:06:02 +0000798
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000799 def __cmp__(self, other):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000800 return cmp(self.pairs, other.pairs)
Guido van Rossum56013131994-06-23 12:06:02 +0000801
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000802 def __hash__(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000803 return hash(self.pairs)
Guido van Rossum56013131994-06-23 12:06:02 +0000804
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000805 def __repr__(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000806 return 'IntSet(%s, %s, %s)' % (`self.tostring()`,
807 `self.sep`, `self.rng`)
Guido van Rossum56013131994-06-23 12:06:02 +0000808
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000809 def normalize(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000810 self.pairs.sort()
811 i = 1
812 while i < len(self.pairs):
813 alo, ahi = self.pairs[i-1]
814 blo, bhi = self.pairs[i]
815 if ahi >= blo-1:
816 self.pairs[i-1:i+1] = [(alo, max(ahi, bhi))]
817 else:
818 i = i+1
Guido van Rossum56013131994-06-23 12:06:02 +0000819
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000820 def tostring(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000821 s = ''
822 for lo, hi in self.pairs:
823 if lo == hi: t = `lo`
824 else: t = `lo` + self.rng + `hi`
825 if s: s = s + (self.sep + t)
826 else: s = t
827 return s
Guido van Rossum56013131994-06-23 12:06:02 +0000828
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000829 def tolist(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000830 l = []
831 for lo, hi in self.pairs:
832 m = range(lo, hi+1)
833 l = l + m
834 return l
Guido van Rossum56013131994-06-23 12:06:02 +0000835
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000836 def fromlist(self, list):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000837 for i in list:
838 self.append(i)
Guido van Rossum56013131994-06-23 12:06:02 +0000839
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000840 def clone(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000841 new = IntSet()
842 new.pairs = self.pairs[:]
843 return new
Guido van Rossum56013131994-06-23 12:06:02 +0000844
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000845 def min(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000846 return self.pairs[0][0]
Guido van Rossum56013131994-06-23 12:06:02 +0000847
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000848 def max(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000849 return self.pairs[-1][-1]
Guido van Rossum56013131994-06-23 12:06:02 +0000850
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000851 def contains(self, x):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000852 for lo, hi in self.pairs:
853 if lo <= x <= hi: return 1
854 return 0
Guido van Rossum56013131994-06-23 12:06:02 +0000855
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000856 def append(self, x):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000857 for i in range(len(self.pairs)):
858 lo, hi = self.pairs[i]
859 if x < lo: # Need to insert before
860 if x+1 == lo:
861 self.pairs[i] = (x, hi)
862 else:
863 self.pairs.insert(i, (x, x))
864 if i > 0 and x-1 == self.pairs[i-1][1]:
865 # Merge with previous
866 self.pairs[i-1:i+1] = [
867 (self.pairs[i-1][0],
868 self.pairs[i][1])
869 ]
870 return
871 if x <= hi: # Already in set
872 return
873 i = len(self.pairs) - 1
874 if i >= 0:
875 lo, hi = self.pairs[i]
876 if x-1 == hi:
877 self.pairs[i] = lo, x
878 return
879 self.pairs.append((x, x))
Guido van Rossum56013131994-06-23 12:06:02 +0000880
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000881 def addpair(self, xlo, xhi):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000882 if xlo > xhi: return
883 self.pairs.append((xlo, xhi))
884 self.normalize()
Guido van Rossum56013131994-06-23 12:06:02 +0000885
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000886 def fromstring(self, data):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000887 new = []
Eric S. Raymond66d99192001-02-09 09:19:27 +0000888 for part in data.split(self.sep):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000889 list = []
Eric S. Raymond66d99192001-02-09 09:19:27 +0000890 for subp in part.split(self.rng):
891 s = subp.strip()
892 list.append(int(s))
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000893 if len(list) == 1:
894 new.append((list[0], list[0]))
895 elif len(list) == 2 and list[0] <= list[1]:
896 new.append((list[0], list[1]))
897 else:
898 raise ValueError, 'bad data passed to IntSet'
899 self.pairs = self.pairs + new
900 self.normalize()
Guido van Rossum56013131994-06-23 12:06:02 +0000901
902
903# Subroutines to read/write entries in .mh_profile and .mh_sequences
904
905def pickline(file, key, casefold = 1):
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000906 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000907 f = open(file, 'r')
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000908 except IOError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000909 return None
Guido van Rossum9694fca1997-10-22 21:00:49 +0000910 pat = re.escape(key) + ':'
911 prog = re.compile(pat, casefold and re.IGNORECASE)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000912 while 1:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000913 line = f.readline()
914 if not line: break
915 if prog.match(line):
916 text = line[len(key)+1:]
917 while 1:
918 line = f.readline()
Eric S. Raymond6e025bc2001-02-10 00:22:33 +0000919 if not line or not line[0].isspace():
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000920 break
921 text = text + line
Eric S. Raymond66d99192001-02-09 09:19:27 +0000922 return text.strip()
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000923 return None
Guido van Rossum56013131994-06-23 12:06:02 +0000924
925def updateline(file, key, value, casefold = 1):
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000926 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000927 f = open(file, 'r')
928 lines = f.readlines()
929 f.close()
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000930 except IOError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000931 lines = []
Guido van Rossum9694fca1997-10-22 21:00:49 +0000932 pat = re.escape(key) + ':(.*)\n'
933 prog = re.compile(pat, casefold and re.IGNORECASE)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000934 if value is None:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000935 newline = None
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000936 else:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000937 newline = '%s: %s\n' % (key, value)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000938 for i in range(len(lines)):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000939 line = lines[i]
940 if prog.match(line):
941 if newline is None:
942 del lines[i]
943 else:
944 lines[i] = newline
945 break
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000946 else:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000947 if newline is not None:
948 lines.append(newline)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000949 tempfile = file + "~"
950 f = open(tempfile, 'w')
951 for line in lines:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000952 f.write(line)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000953 f.close()
954 os.rename(tempfile, file)
Guido van Rossum56013131994-06-23 12:06:02 +0000955
956
957# Test program
958
959def test():
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000960 global mh, f
961 os.system('rm -rf $HOME/Mail/@test')
962 mh = MH()
963 def do(s): print s; print eval(s)
964 do('mh.listfolders()')
965 do('mh.listallfolders()')
966 testfolders = ['@test', '@test/test1', '@test/test2',
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000967 '@test/test1/test11', '@test/test1/test12',
968 '@test/test1/test11/test111']
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000969 for t in testfolders: do('mh.makefolder(%s)' % `t`)
970 do('mh.listsubfolders(\'@test\')')
971 do('mh.listallsubfolders(\'@test\')')
972 f = mh.openfolder('@test')
973 do('f.listsubfolders()')
974 do('f.listallsubfolders()')
975 do('f.getsequences()')
976 seqs = f.getsequences()
977 seqs['foo'] = IntSet('1-10 12-20', ' ').tolist()
978 print seqs
979 f.putsequences(seqs)
980 do('f.getsequences()')
981 testfolders.reverse()
982 for t in testfolders: do('mh.deletefolder(%s)' % `t`)
983 do('mh.getcontext()')
984 context = mh.getcontext()
985 f = mh.openfolder(context)
986 do('f.getcurrent()')
987 for seq in ['first', 'last', 'cur', '.', 'prev', 'next',
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000988 'first:3', 'last:3', 'cur:3', 'cur:-3',
989 'prev:3', 'next:3',
990 '1:3', '1:-3', '100:3', '100:-3', '10000:3', '10000:-3',
991 'all']:
992 try:
993 do('f.parsesequence(%s)' % `seq`)
994 except Error, msg:
995 print "Error:", msg
996 stuff = os.popen("pick %s 2>/dev/null" % `seq`).read()
Eric S. Raymond66d99192001-02-09 09:19:27 +0000997 list = map(int, stuff.split())
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000998 print list, "<-- pick"
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000999 do('f.listmessages()')
Guido van Rossum56013131994-06-23 12:06:02 +00001000
1001
1002if __name__ == '__main__':
Guido van Rossum0c5e0491997-04-16 02:47:12 +00001003 test()