blob: cbe8cb1dd617104ca5c48d1cfdffa6e87a2cbd72 [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 Rossum9694fca1997-10-22 21:00:49 +000077import re
Guido van Rossum56013131994-06-23 12:06:02 +000078import mimetools
79import multifile
Guido van Rossum40b2cfb1995-01-02 18:38:23 +000080import shutil
Guido van Rossum7cfd31e1997-04-16 02:45:08 +000081from bisect import bisect
Guido van Rossum56013131994-06-23 12:06:02 +000082
Skip Montanaro17ab1232001-01-24 06:27:27 +000083__all__ = ["MH","Error","Folder","Message"]
Guido van Rossum56013131994-06-23 12:06:02 +000084
85# Exported constants
86
Fred Drakeffdc48f2000-06-29 05:06:02 +000087class Error(Exception):
88 pass
Guido van Rossum56013131994-06-23 12:06:02 +000089
90
Guido van Rossum56013131994-06-23 12:06:02 +000091class MH:
Guido van Rossum54f22ed2000-02-04 15:10:34 +000092 """Class representing a particular collection of folders.
93 Optional constructor arguments are the pathname for the directory
94 containing the collection, and the MH profile to use.
95 If either is omitted or empty a default is used; the default
96 directory is taken from the MH profile if it is specified there."""
Guido van Rossum56013131994-06-23 12:06:02 +000097
Guido van Rossum0c5e0491997-04-16 02:47:12 +000098 def __init__(self, path = None, profile = None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +000099 """Constructor."""
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000100 if profile is None: profile = MH_PROFILE
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000101 self.profile = os.path.expanduser(profile)
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000102 if path is None: path = self.getprofile('Path')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000103 if not path: path = PATH
104 if not os.path.isabs(path) and path[0] != '~':
105 path = os.path.join('~', path)
106 path = os.path.expanduser(path)
107 if not os.path.isdir(path): raise Error, 'MH() path not found'
108 self.path = path
Guido van Rossum56013131994-06-23 12:06:02 +0000109
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000110 def __repr__(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000111 """String representation."""
Walter Dörwald70a6b492004-02-12 17:35:32 +0000112 return 'MH(%r, %r)' % (self.path, self.profile)
Guido van Rossum56013131994-06-23 12:06:02 +0000113
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000114 def error(self, msg, *args):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000115 """Routine to print an error. May be overridden by a derived class."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000116 sys.stderr.write('MH error: %s\n' % (msg % args))
Guido van Rossum56013131994-06-23 12:06:02 +0000117
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000118 def getprofile(self, key):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000119 """Return a profile entry, None if not found."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000120 return pickline(self.profile, key)
Guido van Rossum56013131994-06-23 12:06:02 +0000121
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000122 def getpath(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000123 """Return the path (the name of the collection's directory)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000124 return self.path
Guido van Rossum56013131994-06-23 12:06:02 +0000125
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000126 def getcontext(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000127 """Return the name of the current folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000128 context = pickline(os.path.join(self.getpath(), 'context'),
129 'Current-Folder')
130 if not context: context = 'inbox'
131 return context
Guido van Rossum56013131994-06-23 12:06:02 +0000132
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000133 def setcontext(self, context):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000134 """Set the name of the current folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000135 fn = os.path.join(self.getpath(), 'context')
136 f = open(fn, "w")
137 f.write("Current-Folder: %s\n" % context)
138 f.close()
Guido van Rossum508a0921996-05-28 22:59:37 +0000139
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000140 def listfolders(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000141 """Return the names of the top-level folders."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000142 folders = []
143 path = self.getpath()
144 for name in os.listdir(path):
145 fullname = os.path.join(path, name)
146 if os.path.isdir(fullname):
147 folders.append(name)
148 folders.sort()
149 return folders
Guido van Rossum56013131994-06-23 12:06:02 +0000150
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000151 def listsubfolders(self, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000152 """Return the names of the subfolders in a given folder
153 (prefixed with the given folder name)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000154 fullname = os.path.join(self.path, name)
155 # Get the link count so we can avoid listing folders
156 # that have no subfolders.
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000157 nlinks = os.stat(fullname).st_nlink
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000158 if nlinks <= 2:
159 return []
160 subfolders = []
161 subnames = os.listdir(fullname)
162 for subname in subnames:
163 fullsubname = os.path.join(fullname, subname)
164 if os.path.isdir(fullsubname):
165 name_subname = os.path.join(name, subname)
166 subfolders.append(name_subname)
167 # Stop looking for subfolders when
168 # we've seen them all
169 nlinks = nlinks - 1
170 if nlinks <= 2:
171 break
172 subfolders.sort()
173 return subfolders
Guido van Rossum56013131994-06-23 12:06:02 +0000174
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000175 def listallfolders(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000176 """Return the names of all folders and subfolders, recursively."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000177 return self.listallsubfolders('')
Guido van Rossum56013131994-06-23 12:06:02 +0000178
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000179 def listallsubfolders(self, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000180 """Return the names of subfolders in a given folder, recursively."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000181 fullname = os.path.join(self.path, name)
182 # Get the link count so we can avoid listing folders
183 # that have no subfolders.
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000184 nlinks = os.stat(fullname).st_nlink
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000185 if nlinks <= 2:
186 return []
187 subfolders = []
188 subnames = os.listdir(fullname)
189 for subname in subnames:
190 if subname[0] == ',' or isnumeric(subname): continue
191 fullsubname = os.path.join(fullname, subname)
192 if os.path.isdir(fullsubname):
193 name_subname = os.path.join(name, subname)
194 subfolders.append(name_subname)
195 if not os.path.islink(fullsubname):
196 subsubfolders = self.listallsubfolders(
197 name_subname)
198 subfolders = subfolders + subsubfolders
199 # Stop looking for subfolders when
200 # we've seen them all
201 nlinks = nlinks - 1
202 if nlinks <= 2:
203 break
204 subfolders.sort()
205 return subfolders
Guido van Rossum56013131994-06-23 12:06:02 +0000206
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000207 def openfolder(self, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000208 """Return a new Folder object for the named folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000209 return Folder(self, name)
Guido van Rossum56013131994-06-23 12:06:02 +0000210
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000211 def makefolder(self, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000212 """Create a new folder (or raise os.error if it cannot be created)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000213 protect = pickline(self.profile, 'Folder-Protect')
214 if protect and isnumeric(protect):
Eric S. Raymond66d99192001-02-09 09:19:27 +0000215 mode = int(protect, 8)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000216 else:
217 mode = FOLDER_PROTECT
218 os.mkdir(os.path.join(self.getpath(), name), mode)
Guido van Rossum56013131994-06-23 12:06:02 +0000219
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000220 def deletefolder(self, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000221 """Delete a folder. This removes files in the folder but not
222 subdirectories. Raise os.error if deleting the folder itself fails."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000223 fullname = os.path.join(self.getpath(), name)
224 for subname in os.listdir(fullname):
225 fullsubname = os.path.join(fullname, subname)
226 try:
227 os.unlink(fullsubname)
228 except os.error:
229 self.error('%s not deleted, continuing...' %
230 fullsubname)
231 os.rmdir(fullname)
Guido van Rossum56013131994-06-23 12:06:02 +0000232
233
Guido van Rossum9694fca1997-10-22 21:00:49 +0000234numericprog = re.compile('^[1-9][0-9]*$')
Guido van Rossum56013131994-06-23 12:06:02 +0000235def isnumeric(str):
Guido van Rossum9694fca1997-10-22 21:00:49 +0000236 return numericprog.match(str) is not None
Guido van Rossum56013131994-06-23 12:06:02 +0000237
238class Folder:
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000239 """Class representing a particular folder."""
Guido van Rossum56013131994-06-23 12:06:02 +0000240
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000241 def __init__(self, mh, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000242 """Constructor."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000243 self.mh = mh
244 self.name = name
245 if not os.path.isdir(self.getfullname()):
246 raise Error, 'no folder %s' % name
Guido van Rossum56013131994-06-23 12:06:02 +0000247
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000248 def __repr__(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000249 """String representation."""
Walter Dörwald70a6b492004-02-12 17:35:32 +0000250 return 'Folder(%r, %r)' % (self.mh, self.name)
Guido van Rossum56013131994-06-23 12:06:02 +0000251
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000252 def error(self, *args):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000253 """Error message handler."""
Guido van Rossum68468eb2003-02-27 20:14:51 +0000254 self.mh.error(*args)
Guido van Rossum56013131994-06-23 12:06:02 +0000255
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000256 def getfullname(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000257 """Return the full pathname of the folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000258 return os.path.join(self.mh.path, self.name)
Guido van Rossum56013131994-06-23 12:06:02 +0000259
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000260 def getsequencesfilename(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000261 """Return the full pathname of the folder's sequences file."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000262 return os.path.join(self.getfullname(), MH_SEQUENCES)
Guido van Rossum56013131994-06-23 12:06:02 +0000263
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000264 def getmessagefilename(self, n):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000265 """Return the full pathname of a message in the folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000266 return os.path.join(self.getfullname(), str(n))
Guido van Rossum56013131994-06-23 12:06:02 +0000267
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000268 def listsubfolders(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000269 """Return list of direct subfolders."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000270 return self.mh.listsubfolders(self.name)
Guido van Rossum56013131994-06-23 12:06:02 +0000271
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000272 def listallsubfolders(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000273 """Return list of all subfolders."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000274 return self.mh.listallsubfolders(self.name)
Guido van Rossum56013131994-06-23 12:06:02 +0000275
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000276 def listmessages(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000277 """Return the list of messages currently present in the folder.
278 As a side effect, set self.last to the last message (or 0)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000279 messages = []
280 match = numericprog.match
281 append = messages.append
282 for name in os.listdir(self.getfullname()):
Guido van Rossumd9d26251998-06-23 14:43:06 +0000283 if match(name):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000284 append(name)
Eric S. Raymond66d99192001-02-09 09:19:27 +0000285 messages = map(int, messages)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000286 messages.sort()
287 if messages:
288 self.last = messages[-1]
289 else:
290 self.last = 0
291 return messages
Guido van Rossum56013131994-06-23 12:06:02 +0000292
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000293 def getsequences(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000294 """Return the set of sequences for the folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000295 sequences = {}
296 fullname = self.getsequencesfilename()
297 try:
298 f = open(fullname, 'r')
299 except IOError:
300 return sequences
301 while 1:
302 line = f.readline()
303 if not line: break
Eric S. Raymond66d99192001-02-09 09:19:27 +0000304 fields = line.split(':')
Fred Drake8152d322000-12-12 23:20:45 +0000305 if len(fields) != 2:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000306 self.error('bad sequence in %s: %s' %
Eric S. Raymond66d99192001-02-09 09:19:27 +0000307 (fullname, line.strip()))
308 key = fields[0].strip()
309 value = IntSet(fields[1].strip(), ' ').tolist()
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000310 sequences[key] = value
311 return sequences
Guido van Rossum56013131994-06-23 12:06:02 +0000312
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000313 def putsequences(self, sequences):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000314 """Write the set of sequences back to the folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000315 fullname = self.getsequencesfilename()
316 f = None
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000317 for key, seq in sequences.items():
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000318 s = IntSet('', ' ')
Raymond Hettinger88f72ff2002-06-04 02:17:04 +0000319 s.fromlist(seq)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000320 if not f: f = open(fullname, 'w')
321 f.write('%s: %s\n' % (key, s.tostring()))
322 if not f:
323 try:
324 os.unlink(fullname)
325 except os.error:
326 pass
327 else:
328 f.close()
Guido van Rossum56013131994-06-23 12:06:02 +0000329
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000330 def getcurrent(self):
Fred Drakeffdc48f2000-06-29 05:06:02 +0000331 """Return the current message. Raise Error when there is none."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000332 seqs = self.getsequences()
333 try:
334 return max(seqs['cur'])
335 except (ValueError, KeyError):
336 raise Error, "no cur message"
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000337
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000338 def setcurrent(self, n):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000339 """Set the current message."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000340 updateline(self.getsequencesfilename(), 'cur', str(n), 0)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000341
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000342 def parsesequence(self, seq):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000343 """Parse an MH sequence specification into a message list.
344 Attempt to mimic mh-sequence(5) as close as possible.
345 Also attempt to mimic observed behavior regarding which
346 conditions cause which error messages."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000347 # XXX Still not complete (see mh-format(5)).
348 # Missing are:
349 # - 'prev', 'next' as count
350 # - Sequence-Negation option
351 all = self.listmessages()
352 # Observed behavior: test for empty folder is done first
353 if not all:
354 raise Error, "no messages in %s" % self.name
355 # Common case first: all is frequently the default
356 if seq == 'all':
357 return all
358 # Test for X:Y before X-Y because 'seq:-n' matches both
Eric S. Raymond66d99192001-02-09 09:19:27 +0000359 i = seq.find(':')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000360 if i >= 0:
361 head, dir, tail = seq[:i], '', seq[i+1:]
362 if tail[:1] in '-+':
363 dir, tail = tail[:1], tail[1:]
364 if not isnumeric(tail):
365 raise Error, "bad message list %s" % seq
366 try:
Eric S. Raymond66d99192001-02-09 09:19:27 +0000367 count = int(tail)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000368 except (ValueError, OverflowError):
369 # Can't use sys.maxint because of i+count below
370 count = len(all)
371 try:
372 anchor = self._parseindex(head, all)
Guido van Rossumb940e112007-01-10 16:19:56 +0000373 except Error as msg:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000374 seqs = self.getsequences()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000375 if not head in seqs:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000376 if not msg:
377 msg = "bad message list %s" % seq
378 raise Error, msg, sys.exc_info()[2]
379 msgs = seqs[head]
380 if not msgs:
381 raise Error, "sequence %s empty" % head
382 if dir == '-':
383 return msgs[-count:]
384 else:
385 return msgs[:count]
386 else:
387 if not dir:
388 if head in ('prev', 'last'):
389 dir = '-'
390 if dir == '-':
391 i = bisect(all, anchor)
392 return all[max(0, i-count):i]
393 else:
394 i = bisect(all, anchor-1)
395 return all[i:i+count]
396 # Test for X-Y next
Eric S. Raymond66d99192001-02-09 09:19:27 +0000397 i = seq.find('-')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000398 if i >= 0:
399 begin = self._parseindex(seq[:i], all)
400 end = self._parseindex(seq[i+1:], all)
401 i = bisect(all, begin-1)
402 j = bisect(all, end)
403 r = all[i:j]
404 if not r:
405 raise Error, "bad message list %s" % seq
406 return r
407 # Neither X:Y nor X-Y; must be a number or a (pseudo-)sequence
408 try:
409 n = self._parseindex(seq, all)
Guido van Rossumb940e112007-01-10 16:19:56 +0000410 except Error as msg:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000411 seqs = self.getsequences()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000412 if not seq in seqs:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000413 if not msg:
414 msg = "bad message list %s" % seq
415 raise Error, msg
416 return seqs[seq]
417 else:
418 if n not in all:
419 if isnumeric(seq):
420 raise Error, "message %d doesn't exist" % n
421 else:
422 raise Error, "no %s message" % seq
423 else:
424 return [n]
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000425
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000426 def _parseindex(self, seq, all):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000427 """Internal: parse a message number (or cur, first, etc.)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000428 if isnumeric(seq):
429 try:
Eric S. Raymond66d99192001-02-09 09:19:27 +0000430 return int(seq)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000431 except (OverflowError, ValueError):
432 return sys.maxint
433 if seq in ('cur', '.'):
434 return self.getcurrent()
435 if seq == 'first':
436 return all[0]
437 if seq == 'last':
438 return all[-1]
439 if seq == 'next':
440 n = self.getcurrent()
441 i = bisect(all, n)
442 try:
443 return all[i]
444 except IndexError:
445 raise Error, "no next message"
446 if seq == 'prev':
447 n = self.getcurrent()
448 i = bisect(all, n-1)
449 if i == 0:
450 raise Error, "no prev message"
451 try:
452 return all[i-1]
453 except IndexError:
454 raise Error, "no prev message"
455 raise Error, None
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000456
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000457 def openmessage(self, n):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000458 """Open a message -- returns a Message object."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000459 return Message(self, n)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000460
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000461 def removemessages(self, list):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000462 """Remove one or more messages -- may raise os.error."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000463 errors = []
464 deleted = []
465 for n in list:
466 path = self.getmessagefilename(n)
467 commapath = self.getmessagefilename(',' + str(n))
468 try:
469 os.unlink(commapath)
470 except os.error:
471 pass
472 try:
473 os.rename(path, commapath)
Guido van Rossumb940e112007-01-10 16:19:56 +0000474 except os.error as msg:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000475 errors.append(msg)
476 else:
477 deleted.append(n)
478 if deleted:
479 self.removefromallsequences(deleted)
480 if errors:
481 if len(errors) == 1:
482 raise os.error, errors[0]
483 else:
484 raise os.error, ('multiple errors:', errors)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000485
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000486 def refilemessages(self, list, tofolder, keepsequences=0):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000487 """Refile one or more messages -- may raise os.error.
488 'tofolder' is an open folder object."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000489 errors = []
490 refiled = {}
491 for n in list:
492 ton = tofolder.getlast() + 1
493 path = self.getmessagefilename(n)
494 topath = tofolder.getmessagefilename(ton)
495 try:
496 os.rename(path, topath)
497 except os.error:
498 # Try copying
499 try:
500 shutil.copy2(path, topath)
501 os.unlink(path)
Guido van Rossumb940e112007-01-10 16:19:56 +0000502 except (IOError, os.error) as msg:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000503 errors.append(msg)
504 try:
505 os.unlink(topath)
506 except os.error:
507 pass
508 continue
509 tofolder.setlast(ton)
510 refiled[n] = ton
511 if refiled:
512 if keepsequences:
513 tofolder._copysequences(self, refiled.items())
514 self.removefromallsequences(refiled.keys())
515 if errors:
516 if len(errors) == 1:
517 raise os.error, errors[0]
518 else:
519 raise os.error, ('multiple errors:', errors)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000520
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000521 def _copysequences(self, fromfolder, refileditems):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000522 """Helper for refilemessages() to copy sequences."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000523 fromsequences = fromfolder.getsequences()
524 tosequences = self.getsequences()
525 changed = 0
526 for name, seq in fromsequences.items():
527 try:
528 toseq = tosequences[name]
529 new = 0
unknown3db163a2001-07-04 07:01:29 +0000530 except KeyError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000531 toseq = []
532 new = 1
533 for fromn, ton in refileditems:
534 if fromn in seq:
535 toseq.append(ton)
536 changed = 1
537 if new and toseq:
538 tosequences[name] = toseq
539 if changed:
540 self.putsequences(tosequences)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000541
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000542 def movemessage(self, n, tofolder, ton):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000543 """Move one message over a specific destination message,
544 which may or may not already exist."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000545 path = self.getmessagefilename(n)
546 # Open it to check that it exists
547 f = open(path)
548 f.close()
549 del f
550 topath = tofolder.getmessagefilename(ton)
551 backuptopath = tofolder.getmessagefilename(',%d' % ton)
552 try:
553 os.rename(topath, backuptopath)
554 except os.error:
555 pass
556 try:
557 os.rename(path, topath)
558 except os.error:
559 # Try copying
560 ok = 0
561 try:
562 tofolder.setlast(None)
563 shutil.copy2(path, topath)
564 ok = 1
565 finally:
566 if not ok:
567 try:
568 os.unlink(topath)
569 except os.error:
570 pass
571 os.unlink(path)
572 self.removefromallsequences([n])
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000573
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000574 def copymessage(self, n, tofolder, ton):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000575 """Copy one message over a specific destination message,
576 which may or may not already exist."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000577 path = self.getmessagefilename(n)
578 # Open it to check that it exists
579 f = open(path)
580 f.close()
581 del f
582 topath = tofolder.getmessagefilename(ton)
583 backuptopath = tofolder.getmessagefilename(',%d' % ton)
584 try:
585 os.rename(topath, backuptopath)
586 except os.error:
587 pass
588 ok = 0
589 try:
590 tofolder.setlast(None)
591 shutil.copy2(path, topath)
592 ok = 1
593 finally:
594 if not ok:
595 try:
596 os.unlink(topath)
597 except os.error:
598 pass
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000599
Guido van Rossum4e5cbcf1997-07-25 14:59:10 +0000600 def createmessage(self, n, txt):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000601 """Create a message, with text from the open file txt."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000602 path = self.getmessagefilename(n)
603 backuppath = self.getmessagefilename(',%d' % n)
604 try:
605 os.rename(path, backuppath)
606 except os.error:
607 pass
608 ok = 0
609 BUFSIZE = 16*1024
610 try:
611 f = open(path, "w")
612 while 1:
613 buf = txt.read(BUFSIZE)
614 if not buf:
615 break
616 f.write(buf)
617 f.close()
618 ok = 1
619 finally:
620 if not ok:
621 try:
622 os.unlink(path)
623 except os.error:
624 pass
Guido van Rossum4e5cbcf1997-07-25 14:59:10 +0000625
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000626 def removefromallsequences(self, list):
Thomas Wouters7e474022000-07-16 12:04:32 +0000627 """Remove one or more messages from all sequences (including last)
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000628 -- but not from 'cur'!!!"""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000629 if hasattr(self, 'last') and self.last in list:
630 del self.last
631 sequences = self.getsequences()
632 changed = 0
633 for name, seq in sequences.items():
634 if name == 'cur':
635 continue
636 for n in list:
637 if n in seq:
638 seq.remove(n)
639 changed = 1
640 if not seq:
641 del sequences[name]
642 if changed:
643 self.putsequences(sequences)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000644
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000645 def getlast(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000646 """Return the last message number."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000647 if not hasattr(self, 'last'):
Guido van Rossumf93befc2001-10-17 05:59:26 +0000648 self.listmessages() # Set self.last
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000649 return self.last
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000650
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000651 def setlast(self, last):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000652 """Set the last message number."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000653 if last is None:
654 if hasattr(self, 'last'):
655 del self.last
656 else:
657 self.last = last
Guido van Rossum56013131994-06-23 12:06:02 +0000658
659class Message(mimetools.Message):
660
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000661 def __init__(self, f, n, fp = None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000662 """Constructor."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000663 self.folder = f
664 self.number = n
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000665 if fp is None:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000666 path = f.getmessagefilename(n)
667 fp = open(path, 'r')
668 mimetools.Message.__init__(self, fp)
Guido van Rossum56013131994-06-23 12:06:02 +0000669
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000670 def __repr__(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000671 """String representation."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000672 return 'Message(%s, %s)' % (repr(self.folder), self.number)
Guido van Rossum56013131994-06-23 12:06:02 +0000673
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000674 def getheadertext(self, pred = None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000675 """Return the message's header text as a string. If an
676 argument is specified, it is used as a filter predicate to
677 decide which headers to return (its argument is the header
678 name converted to lower case)."""
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000679 if pred is None:
Eric S. Raymond66d99192001-02-09 09:19:27 +0000680 return ''.join(self.headers)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000681 headers = []
682 hit = 0
683 for line in self.headers:
Eric S. Raymond6e025bc2001-02-10 00:22:33 +0000684 if not line[0].isspace():
Eric S. Raymond66d99192001-02-09 09:19:27 +0000685 i = line.find(':')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000686 if i > 0:
Eric S. Raymond66d99192001-02-09 09:19:27 +0000687 hit = pred(line[:i].lower())
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000688 if hit: headers.append(line)
Eric S. Raymondc9838f92001-02-09 10:28:34 +0000689 return ''.join(headers)
Guido van Rossum56013131994-06-23 12:06:02 +0000690
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000691 def getbodytext(self, decode = 1):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000692 """Return the message's body text as string. This undoes a
693 Content-Transfer-Encoding, but does not interpret other MIME
694 features (e.g. multipart messages). To suppress decoding,
695 pass 0 as an argument."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000696 self.fp.seek(self.startofbody)
697 encoding = self.getencoding()
Guido van Rossum4fe6caa1999-02-24 16:25:17 +0000698 if not decode or encoding in ('', '7bit', '8bit', 'binary'):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000699 return self.fp.read()
Raymond Hettingera6172712004-12-31 19:15:26 +0000700 try:
701 from cStringIO import StringIO
702 except ImportError:
703 from StringIO import StringIO
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000704 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():
Walter Dörwald70a6b492004-02-12 17:35:32 +0000722 n = "%s.%r" % (self.number, 1 + len(parts))
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000723 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
Michael W. Hudson1ccccc02002-06-02 16:12:06 +0000794 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):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000806 return 'IntSet(%r, %r, %r)' % (self.tostring(), self.sep, self.rng)
Guido van Rossum56013131994-06-23 12:06:02 +0000807
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000808 def normalize(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000809 self.pairs.sort()
810 i = 1
811 while i < len(self.pairs):
812 alo, ahi = self.pairs[i-1]
813 blo, bhi = self.pairs[i]
814 if ahi >= blo-1:
815 self.pairs[i-1:i+1] = [(alo, max(ahi, bhi))]
816 else:
817 i = i+1
Guido van Rossum56013131994-06-23 12:06:02 +0000818
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000819 def tostring(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000820 s = ''
821 for lo, hi in self.pairs:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000822 if lo == hi: t = repr(lo)
823 else: t = repr(lo) + self.rng + repr(hi)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000824 if s: s = s + (self.sep + t)
825 else: s = t
826 return s
Guido van Rossum56013131994-06-23 12:06:02 +0000827
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000828 def tolist(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000829 l = []
830 for lo, hi in self.pairs:
Guido van Rossum805365e2007-05-07 22:24:25 +0000831 m = list(range(lo, hi+1))
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000832 l = l + m
833 return l
Guido van Rossum56013131994-06-23 12:06:02 +0000834
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000835 def fromlist(self, list):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000836 for i in list:
837 self.append(i)
Guido van Rossum56013131994-06-23 12:06:02 +0000838
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000839 def clone(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000840 new = IntSet()
841 new.pairs = self.pairs[:]
842 return new
Guido van Rossum56013131994-06-23 12:06:02 +0000843
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000844 def min(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000845 return self.pairs[0][0]
Guido van Rossum56013131994-06-23 12:06:02 +0000846
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000847 def max(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000848 return self.pairs[-1][-1]
Guido van Rossum56013131994-06-23 12:06:02 +0000849
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000850 def contains(self, x):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000851 for lo, hi in self.pairs:
Tim Petersbc0e9102002-04-04 22:55:58 +0000852 if lo <= x <= hi: return True
853 return False
Guido van Rossum56013131994-06-23 12:06:02 +0000854
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000855 def append(self, x):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000856 for i in range(len(self.pairs)):
857 lo, hi = self.pairs[i]
858 if x < lo: # Need to insert before
859 if x+1 == lo:
860 self.pairs[i] = (x, hi)
861 else:
862 self.pairs.insert(i, (x, x))
863 if i > 0 and x-1 == self.pairs[i-1][1]:
864 # Merge with previous
865 self.pairs[i-1:i+1] = [
866 (self.pairs[i-1][0],
867 self.pairs[i][1])
868 ]
869 return
870 if x <= hi: # Already in set
871 return
872 i = len(self.pairs) - 1
873 if i >= 0:
874 lo, hi = self.pairs[i]
875 if x-1 == hi:
876 self.pairs[i] = lo, x
877 return
878 self.pairs.append((x, x))
Guido van Rossum56013131994-06-23 12:06:02 +0000879
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000880 def addpair(self, xlo, xhi):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000881 if xlo > xhi: return
882 self.pairs.append((xlo, xhi))
883 self.normalize()
Guido van Rossum56013131994-06-23 12:06:02 +0000884
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000885 def fromstring(self, data):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000886 new = []
Eric S. Raymond66d99192001-02-09 09:19:27 +0000887 for part in data.split(self.sep):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000888 list = []
Eric S. Raymond66d99192001-02-09 09:19:27 +0000889 for subp in part.split(self.rng):
890 s = subp.strip()
891 list.append(int(s))
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000892 if len(list) == 1:
893 new.append((list[0], list[0]))
894 elif len(list) == 2 and list[0] <= list[1]:
895 new.append((list[0], list[1]))
896 else:
897 raise ValueError, 'bad data passed to IntSet'
898 self.pairs = self.pairs + new
899 self.normalize()
Guido van Rossum56013131994-06-23 12:06:02 +0000900
901
902# Subroutines to read/write entries in .mh_profile and .mh_sequences
903
904def pickline(file, key, casefold = 1):
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000905 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000906 f = open(file, 'r')
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000907 except IOError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000908 return None
Guido van Rossum9694fca1997-10-22 21:00:49 +0000909 pat = re.escape(key) + ':'
910 prog = re.compile(pat, casefold and re.IGNORECASE)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000911 while 1:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000912 line = f.readline()
913 if not line: break
914 if prog.match(line):
915 text = line[len(key)+1:]
916 while 1:
917 line = f.readline()
Eric S. Raymond6e025bc2001-02-10 00:22:33 +0000918 if not line or not line[0].isspace():
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000919 break
920 text = text + line
Eric S. Raymond66d99192001-02-09 09:19:27 +0000921 return text.strip()
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000922 return None
Guido van Rossum56013131994-06-23 12:06:02 +0000923
924def updateline(file, key, value, casefold = 1):
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000925 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000926 f = open(file, 'r')
927 lines = f.readlines()
928 f.close()
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000929 except IOError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000930 lines = []
Guido van Rossum9694fca1997-10-22 21:00:49 +0000931 pat = re.escape(key) + ':(.*)\n'
932 prog = re.compile(pat, casefold and re.IGNORECASE)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000933 if value is None:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000934 newline = None
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000935 else:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000936 newline = '%s: %s\n' % (key, value)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000937 for i in range(len(lines)):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000938 line = lines[i]
939 if prog.match(line):
940 if newline is None:
941 del lines[i]
942 else:
943 lines[i] = newline
944 break
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000945 else:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000946 if newline is not None:
947 lines.append(newline)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000948 tempfile = file + "~"
949 f = open(tempfile, 'w')
950 for line in lines:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000951 f.write(line)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000952 f.close()
953 os.rename(tempfile, file)
Guido van Rossum56013131994-06-23 12:06:02 +0000954
955
956# Test program
957
958def test():
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000959 global mh, f
960 os.system('rm -rf $HOME/Mail/@test')
961 mh = MH()
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000962 def do(s): print(s); print(eval(s))
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000963 do('mh.listfolders()')
964 do('mh.listallfolders()')
965 testfolders = ['@test', '@test/test1', '@test/test2',
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000966 '@test/test1/test11', '@test/test1/test12',
967 '@test/test1/test11/test111']
Walter Dörwald70a6b492004-02-12 17:35:32 +0000968 for t in testfolders: do('mh.makefolder(%r)' % (t,))
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000969 do('mh.listsubfolders(\'@test\')')
970 do('mh.listallsubfolders(\'@test\')')
971 f = mh.openfolder('@test')
972 do('f.listsubfolders()')
973 do('f.listallsubfolders()')
974 do('f.getsequences()')
975 seqs = f.getsequences()
976 seqs['foo'] = IntSet('1-10 12-20', ' ').tolist()
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000977 print(seqs)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000978 f.putsequences(seqs)
979 do('f.getsequences()')
Walter Dörwald70a6b492004-02-12 17:35:32 +0000980 for t in reversed(testfolders): do('mh.deletefolder(%r)' % (t,))
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000981 do('mh.getcontext()')
982 context = mh.getcontext()
983 f = mh.openfolder(context)
984 do('f.getcurrent()')
Raymond Hettingerdbecd932005-02-06 06:57:08 +0000985 for seq in ('first', 'last', 'cur', '.', 'prev', 'next',
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000986 'first:3', 'last:3', 'cur:3', 'cur:-3',
987 'prev:3', 'next:3',
988 '1:3', '1:-3', '100:3', '100:-3', '10000:3', '10000:-3',
Raymond Hettingerdbecd932005-02-06 06:57:08 +0000989 'all'):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000990 try:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000991 do('f.parsesequence(%r)' % (seq,))
Guido van Rossumb940e112007-01-10 16:19:56 +0000992 except Error as msg:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000993 print("Error:", msg)
Walter Dörwald70a6b492004-02-12 17:35:32 +0000994 stuff = os.popen("pick %r 2>/dev/null" % (seq,)).read()
Eric S. Raymond66d99192001-02-09 09:19:27 +0000995 list = map(int, stuff.split())
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000996 print(list, "<-- pick")
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000997 do('f.listmessages()')
Guido van Rossum56013131994-06-23 12:06:02 +0000998
999
1000if __name__ == '__main__':
Guido van Rossum0c5e0491997-04-16 02:47:12 +00001001 test()