blob: 856e87804cd968ca90a21e27c5be22e30236c1ce [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"""
Brett Cannon2a869132008-05-11 03:01:47 +000055from warnings import warnpy3k
56warnpy3k("the mhlib module has been removed in Python 3.0; use the mailbox "
57 "module instead", stacklevel=2)
58del warnpy3k
Guido van Rossum54f22ed2000-02-04 15:10:34 +000059
Guido van Rossum56013131994-06-23 12:06:02 +000060# XXX To do, functionality:
Guido van Rossum56013131994-06-23 12:06:02 +000061# - annotate messages
Guido van Rossum4fe6caa1999-02-24 16:25:17 +000062# - send messages
Guido van Rossum56013131994-06-23 12:06:02 +000063#
Guido van Rossum40b2cfb1995-01-02 18:38:23 +000064# XXX To do, organization:
Guido van Rossum56013131994-06-23 12:06:02 +000065# - move IntSet to separate file
66# - move most Message functionality to module mimetools
67
68
69# Customizable defaults
70
71MH_PROFILE = '~/.mh_profile'
72PATH = '~/Mail'
73MH_SEQUENCES = '.mh_sequences'
74FOLDER_PROTECT = 0700
75
76
77# Imported modules
78
79import os
Guido van Rossum508a0921996-05-28 22:59:37 +000080import sys
Guido van Rossum9694fca1997-10-22 21:00:49 +000081import re
Guido van Rossum56013131994-06-23 12:06:02 +000082import mimetools
83import multifile
Guido van Rossum40b2cfb1995-01-02 18:38:23 +000084import shutil
Guido van Rossum7cfd31e1997-04-16 02:45:08 +000085from bisect import bisect
Guido van Rossum56013131994-06-23 12:06:02 +000086
Skip Montanaro17ab1232001-01-24 06:27:27 +000087__all__ = ["MH","Error","Folder","Message"]
Guido van Rossum56013131994-06-23 12:06:02 +000088
89# Exported constants
90
Fred Drakeffdc48f2000-06-29 05:06:02 +000091class Error(Exception):
92 pass
Guido van Rossum56013131994-06-23 12:06:02 +000093
94
Guido van Rossum56013131994-06-23 12:06:02 +000095class MH:
Guido van Rossum54f22ed2000-02-04 15:10:34 +000096 """Class representing a particular collection of folders.
97 Optional constructor arguments are the pathname for the directory
98 containing the collection, and the MH profile to use.
99 If either is omitted or empty a default is used; the default
100 directory is taken from the MH profile if it is specified there."""
Guido van Rossum56013131994-06-23 12:06:02 +0000101
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000102 def __init__(self, path = None, profile = None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000103 """Constructor."""
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000104 if profile is None: profile = MH_PROFILE
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000105 self.profile = os.path.expanduser(profile)
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000106 if path is None: path = self.getprofile('Path')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000107 if not path: path = PATH
108 if not os.path.isabs(path) and path[0] != '~':
109 path = os.path.join('~', path)
110 path = os.path.expanduser(path)
111 if not os.path.isdir(path): raise Error, 'MH() path not found'
112 self.path = path
Guido van Rossum56013131994-06-23 12:06:02 +0000113
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000114 def __repr__(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000115 """String representation."""
Walter Dörwald70a6b492004-02-12 17:35:32 +0000116 return 'MH(%r, %r)' % (self.path, self.profile)
Guido van Rossum56013131994-06-23 12:06:02 +0000117
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000118 def error(self, msg, *args):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000119 """Routine to print an error. May be overridden by a derived class."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000120 sys.stderr.write('MH error: %s\n' % (msg % args))
Guido van Rossum56013131994-06-23 12:06:02 +0000121
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000122 def getprofile(self, key):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000123 """Return a profile entry, None if not found."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000124 return pickline(self.profile, key)
Guido van Rossum56013131994-06-23 12:06:02 +0000125
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000126 def getpath(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000127 """Return the path (the name of the collection's directory)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000128 return self.path
Guido van Rossum56013131994-06-23 12:06:02 +0000129
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000130 def getcontext(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000131 """Return the name of the current folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000132 context = pickline(os.path.join(self.getpath(), 'context'),
133 'Current-Folder')
134 if not context: context = 'inbox'
135 return context
Guido van Rossum56013131994-06-23 12:06:02 +0000136
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000137 def setcontext(self, context):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000138 """Set the name of the current folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000139 fn = os.path.join(self.getpath(), 'context')
140 f = open(fn, "w")
141 f.write("Current-Folder: %s\n" % context)
142 f.close()
Guido van Rossum508a0921996-05-28 22:59:37 +0000143
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000144 def listfolders(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000145 """Return the names of the top-level folders."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000146 folders = []
147 path = self.getpath()
148 for name in os.listdir(path):
149 fullname = os.path.join(path, name)
150 if os.path.isdir(fullname):
151 folders.append(name)
152 folders.sort()
153 return folders
Guido van Rossum56013131994-06-23 12:06:02 +0000154
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000155 def listsubfolders(self, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000156 """Return the names of the subfolders in a given folder
157 (prefixed with the given folder name)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000158 fullname = os.path.join(self.path, name)
159 # Get the link count so we can avoid listing folders
160 # that have no subfolders.
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000161 nlinks = os.stat(fullname).st_nlink
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000162 if nlinks <= 2:
163 return []
164 subfolders = []
165 subnames = os.listdir(fullname)
166 for subname in subnames:
167 fullsubname = os.path.join(fullname, subname)
168 if os.path.isdir(fullsubname):
169 name_subname = os.path.join(name, subname)
170 subfolders.append(name_subname)
171 # Stop looking for subfolders when
172 # we've seen them all
173 nlinks = nlinks - 1
174 if nlinks <= 2:
175 break
176 subfolders.sort()
177 return subfolders
Guido van Rossum56013131994-06-23 12:06:02 +0000178
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000179 def listallfolders(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000180 """Return the names of all folders and subfolders, recursively."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000181 return self.listallsubfolders('')
Guido van Rossum56013131994-06-23 12:06:02 +0000182
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000183 def listallsubfolders(self, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000184 """Return the names of subfolders in a given folder, recursively."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000185 fullname = os.path.join(self.path, name)
186 # Get the link count so we can avoid listing folders
187 # that have no subfolders.
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000188 nlinks = os.stat(fullname).st_nlink
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000189 if nlinks <= 2:
190 return []
191 subfolders = []
192 subnames = os.listdir(fullname)
193 for subname in subnames:
194 if subname[0] == ',' or isnumeric(subname): continue
195 fullsubname = os.path.join(fullname, subname)
196 if os.path.isdir(fullsubname):
197 name_subname = os.path.join(name, subname)
198 subfolders.append(name_subname)
199 if not os.path.islink(fullsubname):
200 subsubfolders = self.listallsubfolders(
201 name_subname)
202 subfolders = subfolders + subsubfolders
203 # Stop looking for subfolders when
204 # we've seen them all
205 nlinks = nlinks - 1
206 if nlinks <= 2:
207 break
208 subfolders.sort()
209 return subfolders
Guido van Rossum56013131994-06-23 12:06:02 +0000210
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000211 def openfolder(self, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000212 """Return a new Folder object for the named folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000213 return Folder(self, name)
Guido van Rossum56013131994-06-23 12:06:02 +0000214
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000215 def makefolder(self, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000216 """Create a new folder (or raise os.error if it cannot be created)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000217 protect = pickline(self.profile, 'Folder-Protect')
218 if protect and isnumeric(protect):
Eric S. Raymond66d99192001-02-09 09:19:27 +0000219 mode = int(protect, 8)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000220 else:
221 mode = FOLDER_PROTECT
222 os.mkdir(os.path.join(self.getpath(), name), mode)
Guido van Rossum56013131994-06-23 12:06:02 +0000223
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000224 def deletefolder(self, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000225 """Delete a folder. This removes files in the folder but not
226 subdirectories. Raise os.error if deleting the folder itself fails."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000227 fullname = os.path.join(self.getpath(), name)
228 for subname in os.listdir(fullname):
229 fullsubname = os.path.join(fullname, subname)
230 try:
231 os.unlink(fullsubname)
232 except os.error:
233 self.error('%s not deleted, continuing...' %
234 fullsubname)
235 os.rmdir(fullname)
Guido van Rossum56013131994-06-23 12:06:02 +0000236
237
Guido van Rossum9694fca1997-10-22 21:00:49 +0000238numericprog = re.compile('^[1-9][0-9]*$')
Guido van Rossum56013131994-06-23 12:06:02 +0000239def isnumeric(str):
Guido van Rossum9694fca1997-10-22 21:00:49 +0000240 return numericprog.match(str) is not None
Guido van Rossum56013131994-06-23 12:06:02 +0000241
242class Folder:
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000243 """Class representing a particular folder."""
Guido van Rossum56013131994-06-23 12:06:02 +0000244
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000245 def __init__(self, mh, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000246 """Constructor."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000247 self.mh = mh
248 self.name = name
249 if not os.path.isdir(self.getfullname()):
250 raise Error, 'no folder %s' % name
Guido van Rossum56013131994-06-23 12:06:02 +0000251
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000252 def __repr__(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000253 """String representation."""
Walter Dörwald70a6b492004-02-12 17:35:32 +0000254 return 'Folder(%r, %r)' % (self.mh, self.name)
Guido van Rossum56013131994-06-23 12:06:02 +0000255
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000256 def error(self, *args):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000257 """Error message handler."""
Guido van Rossum68468eb2003-02-27 20:14:51 +0000258 self.mh.error(*args)
Guido van Rossum56013131994-06-23 12:06:02 +0000259
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000260 def getfullname(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000261 """Return the full pathname of the folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000262 return os.path.join(self.mh.path, self.name)
Guido van Rossum56013131994-06-23 12:06:02 +0000263
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000264 def getsequencesfilename(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000265 """Return the full pathname of the folder's sequences file."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000266 return os.path.join(self.getfullname(), MH_SEQUENCES)
Guido van Rossum56013131994-06-23 12:06:02 +0000267
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000268 def getmessagefilename(self, n):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000269 """Return the full pathname of a message in the folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000270 return os.path.join(self.getfullname(), str(n))
Guido van Rossum56013131994-06-23 12:06:02 +0000271
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000272 def listsubfolders(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000273 """Return list of direct subfolders."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000274 return self.mh.listsubfolders(self.name)
Guido van Rossum56013131994-06-23 12:06:02 +0000275
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000276 def listallsubfolders(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000277 """Return list of all subfolders."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000278 return self.mh.listallsubfolders(self.name)
Guido van Rossum56013131994-06-23 12:06:02 +0000279
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000280 def listmessages(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000281 """Return the list of messages currently present in the folder.
282 As a side effect, set self.last to the last message (or 0)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000283 messages = []
284 match = numericprog.match
285 append = messages.append
286 for name in os.listdir(self.getfullname()):
Guido van Rossumd9d26251998-06-23 14:43:06 +0000287 if match(name):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000288 append(name)
Eric S. Raymond66d99192001-02-09 09:19:27 +0000289 messages = map(int, messages)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000290 messages.sort()
291 if messages:
292 self.last = messages[-1]
293 else:
294 self.last = 0
295 return messages
Guido van Rossum56013131994-06-23 12:06:02 +0000296
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000297 def getsequences(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000298 """Return the set of sequences for the folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000299 sequences = {}
300 fullname = self.getsequencesfilename()
301 try:
302 f = open(fullname, 'r')
303 except IOError:
304 return sequences
305 while 1:
306 line = f.readline()
307 if not line: break
Eric S. Raymond66d99192001-02-09 09:19:27 +0000308 fields = line.split(':')
Fred Drake8152d322000-12-12 23:20:45 +0000309 if len(fields) != 2:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000310 self.error('bad sequence in %s: %s' %
Eric S. Raymond66d99192001-02-09 09:19:27 +0000311 (fullname, line.strip()))
312 key = fields[0].strip()
313 value = IntSet(fields[1].strip(), ' ').tolist()
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000314 sequences[key] = value
315 return sequences
Guido van Rossum56013131994-06-23 12:06:02 +0000316
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000317 def putsequences(self, sequences):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000318 """Write the set of sequences back to the folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000319 fullname = self.getsequencesfilename()
320 f = None
Raymond Hettinger88f72ff2002-06-04 02:17:04 +0000321 for key, seq in sequences.iteritems():
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000322 s = IntSet('', ' ')
Raymond Hettinger88f72ff2002-06-04 02:17:04 +0000323 s.fromlist(seq)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000324 if not f: f = open(fullname, 'w')
325 f.write('%s: %s\n' % (key, s.tostring()))
326 if not f:
327 try:
328 os.unlink(fullname)
329 except os.error:
330 pass
331 else:
332 f.close()
Guido van Rossum56013131994-06-23 12:06:02 +0000333
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000334 def getcurrent(self):
Fred Drakeffdc48f2000-06-29 05:06:02 +0000335 """Return the current message. Raise Error when there is none."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000336 seqs = self.getsequences()
337 try:
338 return max(seqs['cur'])
339 except (ValueError, KeyError):
340 raise Error, "no cur message"
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000341
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000342 def setcurrent(self, n):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000343 """Set the current message."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000344 updateline(self.getsequencesfilename(), 'cur', str(n), 0)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000345
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000346 def parsesequence(self, seq):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000347 """Parse an MH sequence specification into a message list.
348 Attempt to mimic mh-sequence(5) as close as possible.
349 Also attempt to mimic observed behavior regarding which
350 conditions cause which error messages."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000351 # XXX Still not complete (see mh-format(5)).
352 # Missing are:
353 # - 'prev', 'next' as count
354 # - Sequence-Negation option
355 all = self.listmessages()
356 # Observed behavior: test for empty folder is done first
357 if not all:
358 raise Error, "no messages in %s" % self.name
359 # Common case first: all is frequently the default
360 if seq == 'all':
361 return all
362 # Test for X:Y before X-Y because 'seq:-n' matches both
Eric S. Raymond66d99192001-02-09 09:19:27 +0000363 i = seq.find(':')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000364 if i >= 0:
365 head, dir, tail = seq[:i], '', seq[i+1:]
366 if tail[:1] in '-+':
367 dir, tail = tail[:1], tail[1:]
368 if not isnumeric(tail):
369 raise Error, "bad message list %s" % seq
370 try:
Eric S. Raymond66d99192001-02-09 09:19:27 +0000371 count = int(tail)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000372 except (ValueError, OverflowError):
373 # Can't use sys.maxint because of i+count below
374 count = len(all)
375 try:
376 anchor = self._parseindex(head, all)
377 except Error, msg:
378 seqs = self.getsequences()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000379 if not head in seqs:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000380 if not msg:
381 msg = "bad message list %s" % seq
382 raise Error, msg, sys.exc_info()[2]
383 msgs = seqs[head]
384 if not msgs:
385 raise Error, "sequence %s empty" % head
386 if dir == '-':
387 return msgs[-count:]
388 else:
389 return msgs[:count]
390 else:
391 if not dir:
392 if head in ('prev', 'last'):
393 dir = '-'
394 if dir == '-':
395 i = bisect(all, anchor)
396 return all[max(0, i-count):i]
397 else:
398 i = bisect(all, anchor-1)
399 return all[i:i+count]
400 # Test for X-Y next
Eric S. Raymond66d99192001-02-09 09:19:27 +0000401 i = seq.find('-')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000402 if i >= 0:
403 begin = self._parseindex(seq[:i], all)
404 end = self._parseindex(seq[i+1:], all)
405 i = bisect(all, begin-1)
406 j = bisect(all, end)
407 r = all[i:j]
408 if not r:
409 raise Error, "bad message list %s" % seq
410 return r
411 # Neither X:Y nor X-Y; must be a number or a (pseudo-)sequence
412 try:
413 n = self._parseindex(seq, all)
414 except Error, msg:
415 seqs = self.getsequences()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000416 if not seq in seqs:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000417 if not msg:
418 msg = "bad message list %s" % seq
419 raise Error, msg
420 return seqs[seq]
421 else:
422 if n not in all:
423 if isnumeric(seq):
424 raise Error, "message %d doesn't exist" % n
425 else:
426 raise Error, "no %s message" % seq
427 else:
428 return [n]
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000429
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000430 def _parseindex(self, seq, all):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000431 """Internal: parse a message number (or cur, first, etc.)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000432 if isnumeric(seq):
433 try:
Eric S. Raymond66d99192001-02-09 09:19:27 +0000434 return int(seq)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000435 except (OverflowError, ValueError):
436 return sys.maxint
437 if seq in ('cur', '.'):
438 return self.getcurrent()
439 if seq == 'first':
440 return all[0]
441 if seq == 'last':
442 return all[-1]
443 if seq == 'next':
444 n = self.getcurrent()
445 i = bisect(all, n)
446 try:
447 return all[i]
448 except IndexError:
449 raise Error, "no next message"
450 if seq == 'prev':
451 n = self.getcurrent()
452 i = bisect(all, n-1)
453 if i == 0:
454 raise Error, "no prev message"
455 try:
456 return all[i-1]
457 except IndexError:
458 raise Error, "no prev message"
459 raise Error, None
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000460
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000461 def openmessage(self, n):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000462 """Open a message -- returns a Message object."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000463 return Message(self, n)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000464
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000465 def removemessages(self, list):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000466 """Remove one or more messages -- may raise os.error."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000467 errors = []
468 deleted = []
469 for n in list:
470 path = self.getmessagefilename(n)
471 commapath = self.getmessagefilename(',' + str(n))
472 try:
473 os.unlink(commapath)
474 except os.error:
475 pass
476 try:
477 os.rename(path, commapath)
478 except os.error, msg:
479 errors.append(msg)
480 else:
481 deleted.append(n)
482 if deleted:
483 self.removefromallsequences(deleted)
484 if errors:
485 if len(errors) == 1:
486 raise os.error, errors[0]
487 else:
488 raise os.error, ('multiple errors:', errors)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000489
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000490 def refilemessages(self, list, tofolder, keepsequences=0):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000491 """Refile one or more messages -- may raise os.error.
492 'tofolder' is an open folder object."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000493 errors = []
494 refiled = {}
495 for n in list:
496 ton = tofolder.getlast() + 1
497 path = self.getmessagefilename(n)
498 topath = tofolder.getmessagefilename(ton)
499 try:
500 os.rename(path, topath)
501 except os.error:
502 # Try copying
503 try:
504 shutil.copy2(path, topath)
505 os.unlink(path)
506 except (IOError, os.error), msg:
507 errors.append(msg)
508 try:
509 os.unlink(topath)
510 except os.error:
511 pass
512 continue
513 tofolder.setlast(ton)
514 refiled[n] = ton
515 if refiled:
516 if keepsequences:
517 tofolder._copysequences(self, refiled.items())
518 self.removefromallsequences(refiled.keys())
519 if errors:
520 if len(errors) == 1:
521 raise os.error, errors[0]
522 else:
523 raise os.error, ('multiple errors:', errors)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000524
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000525 def _copysequences(self, fromfolder, refileditems):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000526 """Helper for refilemessages() to copy sequences."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000527 fromsequences = fromfolder.getsequences()
528 tosequences = self.getsequences()
529 changed = 0
530 for name, seq in fromsequences.items():
531 try:
532 toseq = tosequences[name]
533 new = 0
unknown3db163a2001-07-04 07:01:29 +0000534 except KeyError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000535 toseq = []
536 new = 1
537 for fromn, ton in refileditems:
538 if fromn in seq:
539 toseq.append(ton)
540 changed = 1
541 if new and toseq:
542 tosequences[name] = toseq
543 if changed:
544 self.putsequences(tosequences)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000545
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000546 def movemessage(self, n, tofolder, ton):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000547 """Move one message over a specific destination message,
548 which may or may not already exist."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000549 path = self.getmessagefilename(n)
550 # Open it to check that it exists
551 f = open(path)
552 f.close()
553 del f
554 topath = tofolder.getmessagefilename(ton)
555 backuptopath = tofolder.getmessagefilename(',%d' % ton)
556 try:
557 os.rename(topath, backuptopath)
558 except os.error:
559 pass
560 try:
561 os.rename(path, topath)
562 except os.error:
563 # Try copying
564 ok = 0
565 try:
566 tofolder.setlast(None)
567 shutil.copy2(path, topath)
568 ok = 1
569 finally:
570 if not ok:
571 try:
572 os.unlink(topath)
573 except os.error:
574 pass
575 os.unlink(path)
576 self.removefromallsequences([n])
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000577
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000578 def copymessage(self, n, tofolder, ton):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000579 """Copy one message over a specific destination message,
580 which may or may not already exist."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000581 path = self.getmessagefilename(n)
582 # Open it to check that it exists
583 f = open(path)
584 f.close()
585 del f
586 topath = tofolder.getmessagefilename(ton)
587 backuptopath = tofolder.getmessagefilename(',%d' % ton)
588 try:
589 os.rename(topath, backuptopath)
590 except os.error:
591 pass
592 ok = 0
593 try:
594 tofolder.setlast(None)
595 shutil.copy2(path, topath)
596 ok = 1
597 finally:
598 if not ok:
599 try:
600 os.unlink(topath)
601 except os.error:
602 pass
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000603
Guido van Rossum4e5cbcf1997-07-25 14:59:10 +0000604 def createmessage(self, n, txt):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000605 """Create a message, with text from the open file txt."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000606 path = self.getmessagefilename(n)
607 backuppath = self.getmessagefilename(',%d' % n)
608 try:
609 os.rename(path, backuppath)
610 except os.error:
611 pass
612 ok = 0
613 BUFSIZE = 16*1024
614 try:
615 f = open(path, "w")
616 while 1:
617 buf = txt.read(BUFSIZE)
618 if not buf:
619 break
620 f.write(buf)
621 f.close()
622 ok = 1
623 finally:
624 if not ok:
625 try:
626 os.unlink(path)
627 except os.error:
628 pass
Guido van Rossum4e5cbcf1997-07-25 14:59:10 +0000629
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000630 def removefromallsequences(self, list):
Thomas Wouters7e474022000-07-16 12:04:32 +0000631 """Remove one or more messages from all sequences (including last)
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000632 -- but not from 'cur'!!!"""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000633 if hasattr(self, 'last') and self.last in list:
634 del self.last
635 sequences = self.getsequences()
636 changed = 0
637 for name, seq in sequences.items():
638 if name == 'cur':
639 continue
640 for n in list:
641 if n in seq:
642 seq.remove(n)
643 changed = 1
644 if not seq:
645 del sequences[name]
646 if changed:
647 self.putsequences(sequences)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000648
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000649 def getlast(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000650 """Return the last message number."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000651 if not hasattr(self, 'last'):
Guido van Rossumf93befc2001-10-17 05:59:26 +0000652 self.listmessages() # Set self.last
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000653 return self.last
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000654
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000655 def setlast(self, last):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000656 """Set the last message number."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000657 if last is None:
658 if hasattr(self, 'last'):
659 del self.last
660 else:
661 self.last = last
Guido van Rossum56013131994-06-23 12:06:02 +0000662
663class Message(mimetools.Message):
664
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000665 def __init__(self, f, n, fp = None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000666 """Constructor."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000667 self.folder = f
668 self.number = n
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000669 if fp is None:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000670 path = f.getmessagefilename(n)
671 fp = open(path, 'r')
672 mimetools.Message.__init__(self, fp)
Guido van Rossum56013131994-06-23 12:06:02 +0000673
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000674 def __repr__(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000675 """String representation."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000676 return 'Message(%s, %s)' % (repr(self.folder), self.number)
Guido van Rossum56013131994-06-23 12:06:02 +0000677
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000678 def getheadertext(self, pred = None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000679 """Return the message's header text as a string. If an
680 argument is specified, it is used as a filter predicate to
681 decide which headers to return (its argument is the header
682 name converted to lower case)."""
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000683 if pred is None:
Eric S. Raymond66d99192001-02-09 09:19:27 +0000684 return ''.join(self.headers)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000685 headers = []
686 hit = 0
687 for line in self.headers:
Eric S. Raymond6e025bc2001-02-10 00:22:33 +0000688 if not line[0].isspace():
Eric S. Raymond66d99192001-02-09 09:19:27 +0000689 i = line.find(':')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000690 if i > 0:
Eric S. Raymond66d99192001-02-09 09:19:27 +0000691 hit = pred(line[:i].lower())
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000692 if hit: headers.append(line)
Eric S. Raymondc9838f92001-02-09 10:28:34 +0000693 return ''.join(headers)
Guido van Rossum56013131994-06-23 12:06:02 +0000694
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000695 def getbodytext(self, decode = 1):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000696 """Return the message's body text as string. This undoes a
697 Content-Transfer-Encoding, but does not interpret other MIME
698 features (e.g. multipart messages). To suppress decoding,
699 pass 0 as an argument."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000700 self.fp.seek(self.startofbody)
701 encoding = self.getencoding()
Guido van Rossum4fe6caa1999-02-24 16:25:17 +0000702 if not decode or encoding in ('', '7bit', '8bit', 'binary'):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000703 return self.fp.read()
Raymond Hettingera6172712004-12-31 19:15:26 +0000704 try:
705 from cStringIO import StringIO
706 except ImportError:
707 from StringIO import StringIO
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000708 output = StringIO()
709 mimetools.decode(self.fp, output, encoding)
710 return output.getvalue()
Guido van Rossum56013131994-06-23 12:06:02 +0000711
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000712 def getbodyparts(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000713 """Only for multipart messages: return the message's body as a
714 list of SubMessage objects. Each submessage object behaves
715 (almost) as a Message object."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000716 if self.getmaintype() != 'multipart':
717 raise Error, 'Content-Type is not multipart/*'
718 bdry = self.getparam('boundary')
719 if not bdry:
720 raise Error, 'multipart/* without boundary param'
721 self.fp.seek(self.startofbody)
722 mf = multifile.MultiFile(self.fp)
723 mf.push(bdry)
724 parts = []
725 while mf.next():
Walter Dörwald70a6b492004-02-12 17:35:32 +0000726 n = "%s.%r" % (self.number, 1 + len(parts))
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000727 part = SubMessage(self.folder, n, mf)
728 parts.append(part)
729 mf.pop()
730 return parts
Guido van Rossum56013131994-06-23 12:06:02 +0000731
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000732 def getbody(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000733 """Return body, either a string or a list of messages."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000734 if self.getmaintype() == 'multipart':
735 return self.getbodyparts()
736 else:
737 return self.getbodytext()
Guido van Rossum56013131994-06-23 12:06:02 +0000738
739
740class SubMessage(Message):
741
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000742 def __init__(self, f, n, fp):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000743 """Constructor."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000744 Message.__init__(self, f, n, fp)
745 if self.getmaintype() == 'multipart':
746 self.body = Message.getbodyparts(self)
747 else:
748 self.body = Message.getbodytext(self)
Guido van Rossum4fe6caa1999-02-24 16:25:17 +0000749 self.bodyencoded = Message.getbodytext(self, decode=0)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000750 # XXX If this is big, should remember file pointers
Guido van Rossum56013131994-06-23 12:06:02 +0000751
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000752 def __repr__(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000753 """String representation."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000754 f, n, fp = self.folder, self.number, self.fp
755 return 'SubMessage(%s, %s, %s)' % (f, n, fp)
Guido van Rossum56013131994-06-23 12:06:02 +0000756
Guido van Rossum4fe6caa1999-02-24 16:25:17 +0000757 def getbodytext(self, decode = 1):
758 if not decode:
759 return self.bodyencoded
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 getbodyparts(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000764 if type(self.body) == type([]):
765 return self.body
Guido van Rossum56013131994-06-23 12:06:02 +0000766
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000767 def getbody(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000768 return self.body
Guido van Rossum56013131994-06-23 12:06:02 +0000769
770
Guido van Rossum56013131994-06-23 12:06:02 +0000771class IntSet:
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000772 """Class implementing sets of integers.
773
774 This is an efficient representation for sets consisting of several
775 continuous ranges, e.g. 1-100,200-400,402-1000 is represented
776 internally as a list of three pairs: [(1,100), (200,400),
777 (402,1000)]. The internal representation is always kept normalized.
778
779 The constructor has up to three arguments:
780 - the string used to initialize the set (default ''),
781 - the separator between ranges (default ',')
782 - the separator between begin and end of a range (default '-')
783 The separators must be strings (not regexprs) and should be different.
784
785 The tostring() function yields a string that can be passed to another
786 IntSet constructor; __repr__() is a valid IntSet constructor itself.
787 """
788
789 # XXX The default begin/end separator means that negative numbers are
790 # not supported very well.
791 #
792 # XXX There are currently no operations to remove set elements.
Guido van Rossum56013131994-06-23 12:06:02 +0000793
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000794 def __init__(self, data = None, sep = ',', rng = '-'):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000795 self.pairs = []
796 self.sep = sep
797 self.rng = rng
Michael W. Hudson1ccccc02002-06-02 16:12:06 +0000798 if data: self.fromstring(data)
Guido van Rossum56013131994-06-23 12:06:02 +0000799
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000800 def reset(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000801 self.pairs = []
Guido van Rossum56013131994-06-23 12:06:02 +0000802
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000803 def __cmp__(self, other):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000804 return cmp(self.pairs, other.pairs)
Guido van Rossum56013131994-06-23 12:06:02 +0000805
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000806 def __hash__(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000807 return hash(self.pairs)
Guido van Rossum56013131994-06-23 12:06:02 +0000808
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000809 def __repr__(self):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000810 return 'IntSet(%r, %r, %r)' % (self.tostring(), self.sep, self.rng)
Guido van Rossum56013131994-06-23 12:06:02 +0000811
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000812 def normalize(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000813 self.pairs.sort()
814 i = 1
815 while i < len(self.pairs):
816 alo, ahi = self.pairs[i-1]
817 blo, bhi = self.pairs[i]
818 if ahi >= blo-1:
819 self.pairs[i-1:i+1] = [(alo, max(ahi, bhi))]
820 else:
821 i = i+1
Guido van Rossum56013131994-06-23 12:06:02 +0000822
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000823 def tostring(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000824 s = ''
825 for lo, hi in self.pairs:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000826 if lo == hi: t = repr(lo)
827 else: t = repr(lo) + self.rng + repr(hi)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000828 if s: s = s + (self.sep + t)
829 else: s = t
830 return s
Guido van Rossum56013131994-06-23 12:06:02 +0000831
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000832 def tolist(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000833 l = []
834 for lo, hi in self.pairs:
835 m = range(lo, hi+1)
836 l = l + m
837 return l
Guido van Rossum56013131994-06-23 12:06:02 +0000838
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000839 def fromlist(self, list):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000840 for i in list:
841 self.append(i)
Guido van Rossum56013131994-06-23 12:06:02 +0000842
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000843 def clone(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000844 new = IntSet()
845 new.pairs = self.pairs[:]
846 return new
Guido van Rossum56013131994-06-23 12:06:02 +0000847
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000848 def min(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000849 return self.pairs[0][0]
Guido van Rossum56013131994-06-23 12:06:02 +0000850
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000851 def max(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000852 return self.pairs[-1][-1]
Guido van Rossum56013131994-06-23 12:06:02 +0000853
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000854 def contains(self, x):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000855 for lo, hi in self.pairs:
Tim Petersbc0e9102002-04-04 22:55:58 +0000856 if lo <= x <= hi: return True
857 return False
Guido van Rossum56013131994-06-23 12:06:02 +0000858
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000859 def append(self, x):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000860 for i in range(len(self.pairs)):
861 lo, hi = self.pairs[i]
862 if x < lo: # Need to insert before
863 if x+1 == lo:
864 self.pairs[i] = (x, hi)
865 else:
866 self.pairs.insert(i, (x, x))
867 if i > 0 and x-1 == self.pairs[i-1][1]:
868 # Merge with previous
869 self.pairs[i-1:i+1] = [
870 (self.pairs[i-1][0],
871 self.pairs[i][1])
872 ]
873 return
874 if x <= hi: # Already in set
875 return
876 i = len(self.pairs) - 1
877 if i >= 0:
878 lo, hi = self.pairs[i]
879 if x-1 == hi:
880 self.pairs[i] = lo, x
881 return
882 self.pairs.append((x, x))
Guido van Rossum56013131994-06-23 12:06:02 +0000883
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000884 def addpair(self, xlo, xhi):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000885 if xlo > xhi: return
886 self.pairs.append((xlo, xhi))
887 self.normalize()
Guido van Rossum56013131994-06-23 12:06:02 +0000888
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000889 def fromstring(self, data):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000890 new = []
Eric S. Raymond66d99192001-02-09 09:19:27 +0000891 for part in data.split(self.sep):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000892 list = []
Eric S. Raymond66d99192001-02-09 09:19:27 +0000893 for subp in part.split(self.rng):
894 s = subp.strip()
895 list.append(int(s))
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000896 if len(list) == 1:
897 new.append((list[0], list[0]))
898 elif len(list) == 2 and list[0] <= list[1]:
899 new.append((list[0], list[1]))
900 else:
901 raise ValueError, 'bad data passed to IntSet'
902 self.pairs = self.pairs + new
903 self.normalize()
Guido van Rossum56013131994-06-23 12:06:02 +0000904
905
906# Subroutines to read/write entries in .mh_profile and .mh_sequences
907
908def pickline(file, key, casefold = 1):
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000909 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000910 f = open(file, 'r')
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000911 except IOError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000912 return None
Guido van Rossum9694fca1997-10-22 21:00:49 +0000913 pat = re.escape(key) + ':'
914 prog = re.compile(pat, casefold and re.IGNORECASE)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000915 while 1:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000916 line = f.readline()
917 if not line: break
918 if prog.match(line):
919 text = line[len(key)+1:]
920 while 1:
921 line = f.readline()
Eric S. Raymond6e025bc2001-02-10 00:22:33 +0000922 if not line or not line[0].isspace():
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000923 break
924 text = text + line
Eric S. Raymond66d99192001-02-09 09:19:27 +0000925 return text.strip()
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000926 return None
Guido van Rossum56013131994-06-23 12:06:02 +0000927
928def updateline(file, key, value, casefold = 1):
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000929 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000930 f = open(file, 'r')
931 lines = f.readlines()
932 f.close()
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000933 except IOError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000934 lines = []
Guido van Rossum9694fca1997-10-22 21:00:49 +0000935 pat = re.escape(key) + ':(.*)\n'
936 prog = re.compile(pat, casefold and re.IGNORECASE)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000937 if value is None:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000938 newline = None
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000939 else:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000940 newline = '%s: %s\n' % (key, value)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000941 for i in range(len(lines)):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000942 line = lines[i]
943 if prog.match(line):
944 if newline is None:
945 del lines[i]
946 else:
947 lines[i] = newline
948 break
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000949 else:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000950 if newline is not None:
951 lines.append(newline)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000952 tempfile = file + "~"
953 f = open(tempfile, 'w')
954 for line in lines:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000955 f.write(line)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000956 f.close()
957 os.rename(tempfile, file)
Guido van Rossum56013131994-06-23 12:06:02 +0000958
959
960# Test program
961
962def test():
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000963 global mh, f
964 os.system('rm -rf $HOME/Mail/@test')
965 mh = MH()
966 def do(s): print s; print eval(s)
967 do('mh.listfolders()')
968 do('mh.listallfolders()')
969 testfolders = ['@test', '@test/test1', '@test/test2',
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000970 '@test/test1/test11', '@test/test1/test12',
971 '@test/test1/test11/test111']
Walter Dörwald70a6b492004-02-12 17:35:32 +0000972 for t in testfolders: do('mh.makefolder(%r)' % (t,))
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000973 do('mh.listsubfolders(\'@test\')')
974 do('mh.listallsubfolders(\'@test\')')
975 f = mh.openfolder('@test')
976 do('f.listsubfolders()')
977 do('f.listallsubfolders()')
978 do('f.getsequences()')
979 seqs = f.getsequences()
980 seqs['foo'] = IntSet('1-10 12-20', ' ').tolist()
981 print seqs
982 f.putsequences(seqs)
983 do('f.getsequences()')
Walter Dörwald70a6b492004-02-12 17:35:32 +0000984 for t in reversed(testfolders): do('mh.deletefolder(%r)' % (t,))
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000985 do('mh.getcontext()')
986 context = mh.getcontext()
987 f = mh.openfolder(context)
988 do('f.getcurrent()')
Raymond Hettingerdbecd932005-02-06 06:57:08 +0000989 for seq in ('first', 'last', 'cur', '.', 'prev', 'next',
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000990 'first:3', 'last:3', 'cur:3', 'cur:-3',
991 'prev:3', 'next:3',
992 '1:3', '1:-3', '100:3', '100:-3', '10000:3', '10000:-3',
Raymond Hettingerdbecd932005-02-06 06:57:08 +0000993 'all'):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000994 try:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000995 do('f.parsesequence(%r)' % (seq,))
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000996 except Error, msg:
997 print "Error:", msg
Walter Dörwald70a6b492004-02-12 17:35:32 +0000998 stuff = os.popen("pick %r 2>/dev/null" % (seq,)).read()
Eric S. Raymond66d99192001-02-09 09:19:27 +0000999 list = map(int, stuff.split())
Guido van Rossum45e2fbc1998-03-26 21:13:24 +00001000 print list, "<-- pick"
Guido van Rossum0c5e0491997-04-16 02:47:12 +00001001 do('f.listmessages()')
Guido van Rossum56013131994-06-23 12:06:02 +00001002
1003
1004if __name__ == '__main__':
Guido van Rossum0c5e0491997-04-16 02:47:12 +00001005 test()