blob: b42843944f262942a40e6af9f5622572b52aa905 [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'
Guido van Rossumcd16bf62007-06-13 18:07:49 +000070FOLDER_PROTECT = 0o700
Guido van Rossum56013131994-06-23 12:06:02 +000071
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)
Collin Winterce36ad82007-08-30 01:19:48 +0000107 if not os.path.isdir(path): raise Error('MH() path not found')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000108 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()):
Collin Winterce36ad82007-08-30 01:19:48 +0000246 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)
Guido van Rossumc1f779c2007-07-03 08:25:58 +0000285 messages = sorted(map(int, messages))
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000286 if messages:
287 self.last = messages[-1]
288 else:
289 self.last = 0
290 return messages
Guido van Rossum56013131994-06-23 12:06:02 +0000291
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000292 def getsequences(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000293 """Return the set of sequences for the folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000294 sequences = {}
295 fullname = self.getsequencesfilename()
296 try:
297 f = open(fullname, 'r')
298 except IOError:
299 return sequences
300 while 1:
301 line = f.readline()
302 if not line: break
Eric S. Raymond66d99192001-02-09 09:19:27 +0000303 fields = line.split(':')
Fred Drake8152d322000-12-12 23:20:45 +0000304 if len(fields) != 2:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000305 self.error('bad sequence in %s: %s' %
Eric S. Raymond66d99192001-02-09 09:19:27 +0000306 (fullname, line.strip()))
307 key = fields[0].strip()
308 value = IntSet(fields[1].strip(), ' ').tolist()
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000309 sequences[key] = value
310 return sequences
Guido van Rossum56013131994-06-23 12:06:02 +0000311
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000312 def putsequences(self, sequences):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000313 """Write the set of sequences back to the folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000314 fullname = self.getsequencesfilename()
315 f = None
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000316 for key, seq in sequences.items():
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000317 s = IntSet('', ' ')
Raymond Hettinger88f72ff2002-06-04 02:17:04 +0000318 s.fromlist(seq)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000319 if not f: f = open(fullname, 'w')
320 f.write('%s: %s\n' % (key, s.tostring()))
321 if not f:
322 try:
323 os.unlink(fullname)
324 except os.error:
325 pass
326 else:
327 f.close()
Guido van Rossum56013131994-06-23 12:06:02 +0000328
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000329 def getcurrent(self):
Fred Drakeffdc48f2000-06-29 05:06:02 +0000330 """Return the current message. Raise Error when there is none."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000331 seqs = self.getsequences()
332 try:
333 return max(seqs['cur'])
334 except (ValueError, KeyError):
Collin Winterce36ad82007-08-30 01:19:48 +0000335 raise Error("no cur message")
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000336
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000337 def setcurrent(self, n):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000338 """Set the current message."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000339 updateline(self.getsequencesfilename(), 'cur', str(n), 0)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000340
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000341 def parsesequence(self, seq):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000342 """Parse an MH sequence specification into a message list.
343 Attempt to mimic mh-sequence(5) as close as possible.
344 Also attempt to mimic observed behavior regarding which
345 conditions cause which error messages."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000346 # XXX Still not complete (see mh-format(5)).
347 # Missing are:
348 # - 'prev', 'next' as count
349 # - Sequence-Negation option
350 all = self.listmessages()
351 # Observed behavior: test for empty folder is done first
352 if not all:
Collin Winterce36ad82007-08-30 01:19:48 +0000353 raise Error("no messages in %s" % self.name)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000354 # Common case first: all is frequently the default
355 if seq == 'all':
356 return all
357 # Test for X:Y before X-Y because 'seq:-n' matches both
Eric S. Raymond66d99192001-02-09 09:19:27 +0000358 i = seq.find(':')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000359 if i >= 0:
360 head, dir, tail = seq[:i], '', seq[i+1:]
361 if tail[:1] in '-+':
362 dir, tail = tail[:1], tail[1:]
363 if not isnumeric(tail):
Collin Winterce36ad82007-08-30 01:19:48 +0000364 raise Error("bad message list %s" % seq)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000365 try:
Eric S. Raymond66d99192001-02-09 09:19:27 +0000366 count = int(tail)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000367 except (ValueError, OverflowError):
Christian Heimesa37d4c62007-12-04 23:02:19 +0000368 # Can't use sys.maxsize because of i+count below
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000369 count = len(all)
370 try:
371 anchor = self._parseindex(head, all)
Guido van Rossumb940e112007-01-10 16:19:56 +0000372 except Error as msg:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000373 seqs = self.getsequences()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000374 if not head in seqs:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000375 if not msg:
376 msg = "bad message list %s" % seq
Collin Winterce36ad82007-08-30 01:19:48 +0000377 raise Error(msg).with_traceback(sys.exc_info()[2])
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000378 msgs = seqs[head]
379 if not msgs:
Collin Winterce36ad82007-08-30 01:19:48 +0000380 raise Error("sequence %s empty" % head)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000381 if dir == '-':
382 return msgs[-count:]
383 else:
384 return msgs[:count]
385 else:
386 if not dir:
387 if head in ('prev', 'last'):
388 dir = '-'
389 if dir == '-':
390 i = bisect(all, anchor)
391 return all[max(0, i-count):i]
392 else:
393 i = bisect(all, anchor-1)
394 return all[i:i+count]
395 # Test for X-Y next
Eric S. Raymond66d99192001-02-09 09:19:27 +0000396 i = seq.find('-')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000397 if i >= 0:
398 begin = self._parseindex(seq[:i], all)
399 end = self._parseindex(seq[i+1:], all)
400 i = bisect(all, begin-1)
401 j = bisect(all, end)
402 r = all[i:j]
403 if not r:
Collin Winterce36ad82007-08-30 01:19:48 +0000404 raise Error("bad message list %s" % seq)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000405 return r
406 # Neither X:Y nor X-Y; must be a number or a (pseudo-)sequence
407 try:
408 n = self._parseindex(seq, all)
Guido van Rossumb940e112007-01-10 16:19:56 +0000409 except Error as msg:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000410 seqs = self.getsequences()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000411 if not seq in seqs:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000412 if not msg:
413 msg = "bad message list %s" % seq
Collin Winterce36ad82007-08-30 01:19:48 +0000414 raise Error(msg)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000415 return seqs[seq]
416 else:
417 if n not in all:
418 if isnumeric(seq):
Collin Winterce36ad82007-08-30 01:19:48 +0000419 raise Error("message %d doesn't exist" % n)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000420 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000421 raise Error("no %s message" % seq)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000422 else:
423 return [n]
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000424
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000425 def _parseindex(self, seq, all):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000426 """Internal: parse a message number (or cur, first, etc.)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000427 if isnumeric(seq):
428 try:
Eric S. Raymond66d99192001-02-09 09:19:27 +0000429 return int(seq)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000430 except (OverflowError, ValueError):
Christian Heimesa37d4c62007-12-04 23:02:19 +0000431 return sys.maxsize
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000432 if seq in ('cur', '.'):
433 return self.getcurrent()
434 if seq == 'first':
435 return all[0]
436 if seq == 'last':
437 return all[-1]
438 if seq == 'next':
439 n = self.getcurrent()
440 i = bisect(all, n)
441 try:
442 return all[i]
443 except IndexError:
Collin Winterce36ad82007-08-30 01:19:48 +0000444 raise Error("no next message")
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000445 if seq == 'prev':
446 n = self.getcurrent()
447 i = bisect(all, n-1)
448 if i == 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000449 raise Error("no prev message")
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000450 try:
451 return all[i-1]
452 except IndexError:
Collin Winterce36ad82007-08-30 01:19:48 +0000453 raise Error("no prev message")
454 raise Error()
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000455
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000456 def openmessage(self, n):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000457 """Open a message -- returns a Message object."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000458 return Message(self, n)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000459
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000460 def removemessages(self, list):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000461 """Remove one or more messages -- may raise os.error."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000462 errors = []
463 deleted = []
464 for n in list:
465 path = self.getmessagefilename(n)
466 commapath = self.getmessagefilename(',' + str(n))
467 try:
468 os.unlink(commapath)
469 except os.error:
470 pass
471 try:
472 os.rename(path, commapath)
Guido van Rossumb940e112007-01-10 16:19:56 +0000473 except os.error as msg:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000474 errors.append(msg)
475 else:
476 deleted.append(n)
477 if deleted:
478 self.removefromallsequences(deleted)
479 if errors:
480 if len(errors) == 1:
Collin Winterce36ad82007-08-30 01:19:48 +0000481 raise os.error(errors[0])
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000482 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000483 raise os.error('multiple errors:', errors)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000484
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000485 def refilemessages(self, list, tofolder, keepsequences=0):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000486 """Refile one or more messages -- may raise os.error.
487 'tofolder' is an open folder object."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000488 errors = []
489 refiled = {}
490 for n in list:
491 ton = tofolder.getlast() + 1
492 path = self.getmessagefilename(n)
493 topath = tofolder.getmessagefilename(ton)
494 try:
495 os.rename(path, topath)
496 except os.error:
497 # Try copying
498 try:
499 shutil.copy2(path, topath)
500 os.unlink(path)
Guido van Rossumb940e112007-01-10 16:19:56 +0000501 except (IOError, os.error) as msg:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000502 errors.append(msg)
503 try:
504 os.unlink(topath)
505 except os.error:
506 pass
507 continue
508 tofolder.setlast(ton)
509 refiled[n] = ton
510 if refiled:
511 if keepsequences:
512 tofolder._copysequences(self, refiled.items())
513 self.removefromallsequences(refiled.keys())
514 if errors:
515 if len(errors) == 1:
Collin Winterce36ad82007-08-30 01:19:48 +0000516 raise os.error(errors[0])
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000517 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000518 raise os.error('multiple errors:', errors)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000519
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000520 def _copysequences(self, fromfolder, refileditems):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000521 """Helper for refilemessages() to copy sequences."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000522 fromsequences = fromfolder.getsequences()
523 tosequences = self.getsequences()
524 changed = 0
525 for name, seq in fromsequences.items():
526 try:
527 toseq = tosequences[name]
528 new = 0
unknown3db163a2001-07-04 07:01:29 +0000529 except KeyError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000530 toseq = []
531 new = 1
532 for fromn, ton in refileditems:
533 if fromn in seq:
534 toseq.append(ton)
535 changed = 1
536 if new and toseq:
537 tosequences[name] = toseq
538 if changed:
539 self.putsequences(tosequences)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000540
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000541 def movemessage(self, n, tofolder, ton):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000542 """Move one message over a specific destination message,
543 which may or may not already exist."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000544 path = self.getmessagefilename(n)
545 # Open it to check that it exists
546 f = open(path)
547 f.close()
548 del f
549 topath = tofolder.getmessagefilename(ton)
550 backuptopath = tofolder.getmessagefilename(',%d' % ton)
551 try:
552 os.rename(topath, backuptopath)
553 except os.error:
554 pass
555 try:
556 os.rename(path, topath)
557 except os.error:
558 # Try copying
559 ok = 0
560 try:
561 tofolder.setlast(None)
562 shutil.copy2(path, topath)
563 ok = 1
564 finally:
565 if not ok:
566 try:
567 os.unlink(topath)
568 except os.error:
569 pass
570 os.unlink(path)
571 self.removefromallsequences([n])
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000572
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000573 def copymessage(self, n, tofolder, ton):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000574 """Copy one message over a specific destination message,
575 which may or may not already exist."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000576 path = self.getmessagefilename(n)
577 # Open it to check that it exists
578 f = open(path)
579 f.close()
580 del f
581 topath = tofolder.getmessagefilename(ton)
582 backuptopath = tofolder.getmessagefilename(',%d' % ton)
583 try:
584 os.rename(topath, backuptopath)
585 except os.error:
586 pass
587 ok = 0
588 try:
589 tofolder.setlast(None)
590 shutil.copy2(path, topath)
591 ok = 1
592 finally:
593 if not ok:
594 try:
595 os.unlink(topath)
596 except os.error:
597 pass
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000598
Guido van Rossum4e5cbcf1997-07-25 14:59:10 +0000599 def createmessage(self, n, txt):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000600 """Create a message, with text from the open file txt."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000601 path = self.getmessagefilename(n)
602 backuppath = self.getmessagefilename(',%d' % n)
603 try:
604 os.rename(path, backuppath)
605 except os.error:
606 pass
607 ok = 0
608 BUFSIZE = 16*1024
609 try:
610 f = open(path, "w")
611 while 1:
612 buf = txt.read(BUFSIZE)
613 if not buf:
614 break
615 f.write(buf)
616 f.close()
617 ok = 1
618 finally:
619 if not ok:
620 try:
621 os.unlink(path)
622 except os.error:
623 pass
Guido van Rossum4e5cbcf1997-07-25 14:59:10 +0000624
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000625 def removefromallsequences(self, list):
Thomas Wouters7e474022000-07-16 12:04:32 +0000626 """Remove one or more messages from all sequences (including last)
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000627 -- but not from 'cur'!!!"""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000628 if hasattr(self, 'last') and self.last in list:
629 del self.last
630 sequences = self.getsequences()
631 changed = 0
632 for name, seq in sequences.items():
633 if name == 'cur':
634 continue
635 for n in list:
636 if n in seq:
637 seq.remove(n)
638 changed = 1
639 if not seq:
640 del sequences[name]
641 if changed:
642 self.putsequences(sequences)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000643
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000644 def getlast(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000645 """Return the last message number."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000646 if not hasattr(self, 'last'):
Guido van Rossumf93befc2001-10-17 05:59:26 +0000647 self.listmessages() # Set self.last
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000648 return self.last
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000649
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000650 def setlast(self, last):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000651 """Set the last message number."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000652 if last is None:
653 if hasattr(self, 'last'):
654 del self.last
655 else:
656 self.last = last
Guido van Rossum56013131994-06-23 12:06:02 +0000657
658class Message(mimetools.Message):
659
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000660 def __init__(self, f, n, fp = None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000661 """Constructor."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000662 self.folder = f
663 self.number = n
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000664 if fp is None:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000665 path = f.getmessagefilename(n)
666 fp = open(path, 'r')
667 mimetools.Message.__init__(self, fp)
Guido van Rossum56013131994-06-23 12:06:02 +0000668
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000669 def __repr__(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000670 """String representation."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000671 return 'Message(%s, %s)' % (repr(self.folder), self.number)
Guido van Rossum56013131994-06-23 12:06:02 +0000672
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000673 def getheadertext(self, pred = None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000674 """Return the message's header text as a string. If an
675 argument is specified, it is used as a filter predicate to
676 decide which headers to return (its argument is the header
677 name converted to lower case)."""
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000678 if pred is None:
Eric S. Raymond66d99192001-02-09 09:19:27 +0000679 return ''.join(self.headers)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000680 headers = []
681 hit = 0
682 for line in self.headers:
Eric S. Raymond6e025bc2001-02-10 00:22:33 +0000683 if not line[0].isspace():
Eric S. Raymond66d99192001-02-09 09:19:27 +0000684 i = line.find(':')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000685 if i > 0:
Eric S. Raymond66d99192001-02-09 09:19:27 +0000686 hit = pred(line[:i].lower())
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000687 if hit: headers.append(line)
Eric S. Raymondc9838f92001-02-09 10:28:34 +0000688 return ''.join(headers)
Guido van Rossum56013131994-06-23 12:06:02 +0000689
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000690 def getbodytext(self, decode = 1):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000691 """Return the message's body text as string. This undoes a
692 Content-Transfer-Encoding, but does not interpret other MIME
693 features (e.g. multipart messages). To suppress decoding,
694 pass 0 as an argument."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000695 self.fp.seek(self.startofbody)
696 encoding = self.getencoding()
Guido van Rossum4fe6caa1999-02-24 16:25:17 +0000697 if not decode or encoding in ('', '7bit', '8bit', 'binary'):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000698 return self.fp.read()
Guido van Rossum68937b42007-05-18 00:51:22 +0000699 from io import StringIO
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000700 output = StringIO()
701 mimetools.decode(self.fp, output, encoding)
702 return output.getvalue()
Guido van Rossum56013131994-06-23 12:06:02 +0000703
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000704 def getbodyparts(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000705 """Only for multipart messages: return the message's body as a
706 list of SubMessage objects. Each submessage object behaves
707 (almost) as a Message object."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000708 if self.getmaintype() != 'multipart':
Collin Winterce36ad82007-08-30 01:19:48 +0000709 raise Error('Content-Type is not multipart/*')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000710 bdry = self.getparam('boundary')
711 if not bdry:
Collin Winterce36ad82007-08-30 01:19:48 +0000712 raise Error('multipart/* without boundary param')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000713 self.fp.seek(self.startofbody)
714 mf = multifile.MultiFile(self.fp)
715 mf.push(bdry)
716 parts = []
717 while mf.next():
Walter Dörwald70a6b492004-02-12 17:35:32 +0000718 n = "%s.%r" % (self.number, 1 + len(parts))
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000719 part = SubMessage(self.folder, n, mf)
720 parts.append(part)
721 mf.pop()
722 return parts
Guido van Rossum56013131994-06-23 12:06:02 +0000723
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000724 def getbody(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000725 """Return body, either a string or a list of messages."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000726 if self.getmaintype() == 'multipart':
727 return self.getbodyparts()
728 else:
729 return self.getbodytext()
Guido van Rossum56013131994-06-23 12:06:02 +0000730
731
732class SubMessage(Message):
733
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000734 def __init__(self, f, n, fp):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000735 """Constructor."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000736 Message.__init__(self, f, n, fp)
737 if self.getmaintype() == 'multipart':
738 self.body = Message.getbodyparts(self)
739 else:
740 self.body = Message.getbodytext(self)
Guido van Rossum4fe6caa1999-02-24 16:25:17 +0000741 self.bodyencoded = Message.getbodytext(self, decode=0)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000742 # XXX If this is big, should remember file pointers
Guido van Rossum56013131994-06-23 12:06:02 +0000743
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000744 def __repr__(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000745 """String representation."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000746 f, n, fp = self.folder, self.number, self.fp
747 return 'SubMessage(%s, %s, %s)' % (f, n, fp)
Guido van Rossum56013131994-06-23 12:06:02 +0000748
Guido van Rossum4fe6caa1999-02-24 16:25:17 +0000749 def getbodytext(self, decode = 1):
750 if not decode:
751 return self.bodyencoded
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000752 if type(self.body) == type(''):
753 return self.body
Guido van Rossum56013131994-06-23 12:06:02 +0000754
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000755 def getbodyparts(self):
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 getbody(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000760 return self.body
Guido van Rossum56013131994-06-23 12:06:02 +0000761
762
Guido van Rossum56013131994-06-23 12:06:02 +0000763class IntSet:
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000764 """Class implementing sets of integers.
765
766 This is an efficient representation for sets consisting of several
767 continuous ranges, e.g. 1-100,200-400,402-1000 is represented
768 internally as a list of three pairs: [(1,100), (200,400),
769 (402,1000)]. The internal representation is always kept normalized.
770
771 The constructor has up to three arguments:
772 - the string used to initialize the set (default ''),
773 - the separator between ranges (default ',')
774 - the separator between begin and end of a range (default '-')
775 The separators must be strings (not regexprs) and should be different.
776
777 The tostring() function yields a string that can be passed to another
778 IntSet constructor; __repr__() is a valid IntSet constructor itself.
779 """
780
781 # XXX The default begin/end separator means that negative numbers are
782 # not supported very well.
783 #
784 # XXX There are currently no operations to remove set elements.
Guido van Rossum56013131994-06-23 12:06:02 +0000785
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000786 def __init__(self, data = None, sep = ',', rng = '-'):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000787 self.pairs = []
788 self.sep = sep
789 self.rng = rng
Michael W. Hudson1ccccc02002-06-02 16:12:06 +0000790 if data: self.fromstring(data)
Guido van Rossum56013131994-06-23 12:06:02 +0000791
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000792 def reset(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000793 self.pairs = []
Guido van Rossum56013131994-06-23 12:06:02 +0000794
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000795 def __cmp__(self, other):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000796 return cmp(self.pairs, other.pairs)
Guido van Rossum56013131994-06-23 12:06:02 +0000797
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000798 def __hash__(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000799 return hash(self.pairs)
Guido van Rossum56013131994-06-23 12:06:02 +0000800
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000801 def __repr__(self):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000802 return 'IntSet(%r, %r, %r)' % (self.tostring(), self.sep, self.rng)
Guido van Rossum56013131994-06-23 12:06:02 +0000803
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000804 def normalize(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000805 self.pairs.sort()
806 i = 1
807 while i < len(self.pairs):
808 alo, ahi = self.pairs[i-1]
809 blo, bhi = self.pairs[i]
810 if ahi >= blo-1:
811 self.pairs[i-1:i+1] = [(alo, max(ahi, bhi))]
812 else:
813 i = i+1
Guido van Rossum56013131994-06-23 12:06:02 +0000814
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000815 def tostring(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000816 s = ''
817 for lo, hi in self.pairs:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000818 if lo == hi: t = repr(lo)
819 else: t = repr(lo) + self.rng + repr(hi)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000820 if s: s = s + (self.sep + t)
821 else: s = t
822 return s
Guido van Rossum56013131994-06-23 12:06:02 +0000823
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000824 def tolist(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000825 l = []
826 for lo, hi in self.pairs:
Guido van Rossum805365e2007-05-07 22:24:25 +0000827 m = list(range(lo, hi+1))
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000828 l = l + m
829 return l
Guido van Rossum56013131994-06-23 12:06:02 +0000830
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000831 def fromlist(self, list):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000832 for i in list:
833 self.append(i)
Guido van Rossum56013131994-06-23 12:06:02 +0000834
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000835 def clone(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000836 new = IntSet()
837 new.pairs = self.pairs[:]
838 return new
Guido van Rossum56013131994-06-23 12:06:02 +0000839
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000840 def min(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000841 return self.pairs[0][0]
Guido van Rossum56013131994-06-23 12:06:02 +0000842
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000843 def max(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000844 return self.pairs[-1][-1]
Guido van Rossum56013131994-06-23 12:06:02 +0000845
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000846 def contains(self, x):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000847 for lo, hi in self.pairs:
Tim Petersbc0e9102002-04-04 22:55:58 +0000848 if lo <= x <= hi: return True
849 return False
Guido van Rossum56013131994-06-23 12:06:02 +0000850
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000851 def append(self, x):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000852 for i in range(len(self.pairs)):
853 lo, hi = self.pairs[i]
854 if x < lo: # Need to insert before
855 if x+1 == lo:
856 self.pairs[i] = (x, hi)
857 else:
858 self.pairs.insert(i, (x, x))
859 if i > 0 and x-1 == self.pairs[i-1][1]:
860 # Merge with previous
861 self.pairs[i-1:i+1] = [
862 (self.pairs[i-1][0],
863 self.pairs[i][1])
864 ]
865 return
866 if x <= hi: # Already in set
867 return
868 i = len(self.pairs) - 1
869 if i >= 0:
870 lo, hi = self.pairs[i]
871 if x-1 == hi:
872 self.pairs[i] = lo, x
873 return
874 self.pairs.append((x, x))
Guido van Rossum56013131994-06-23 12:06:02 +0000875
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000876 def addpair(self, xlo, xhi):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000877 if xlo > xhi: return
878 self.pairs.append((xlo, xhi))
879 self.normalize()
Guido van Rossum56013131994-06-23 12:06:02 +0000880
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000881 def fromstring(self, data):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000882 new = []
Eric S. Raymond66d99192001-02-09 09:19:27 +0000883 for part in data.split(self.sep):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000884 list = []
Eric S. Raymond66d99192001-02-09 09:19:27 +0000885 for subp in part.split(self.rng):
886 s = subp.strip()
887 list.append(int(s))
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000888 if len(list) == 1:
889 new.append((list[0], list[0]))
890 elif len(list) == 2 and list[0] <= list[1]:
891 new.append((list[0], list[1]))
892 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000893 raise ValueError('bad data passed to IntSet')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000894 self.pairs = self.pairs + new
895 self.normalize()
Guido van Rossum56013131994-06-23 12:06:02 +0000896
897
898# Subroutines to read/write entries in .mh_profile and .mh_sequences
899
900def pickline(file, key, casefold = 1):
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000901 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000902 f = open(file, 'r')
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000903 except IOError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000904 return None
Guido van Rossum9694fca1997-10-22 21:00:49 +0000905 pat = re.escape(key) + ':'
906 prog = re.compile(pat, casefold and re.IGNORECASE)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000907 while 1:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000908 line = f.readline()
909 if not line: break
910 if prog.match(line):
911 text = line[len(key)+1:]
912 while 1:
913 line = f.readline()
Eric S. Raymond6e025bc2001-02-10 00:22:33 +0000914 if not line or not line[0].isspace():
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000915 break
916 text = text + line
Eric S. Raymond66d99192001-02-09 09:19:27 +0000917 return text.strip()
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000918 return None
Guido van Rossum56013131994-06-23 12:06:02 +0000919
920def updateline(file, key, value, casefold = 1):
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000921 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000922 f = open(file, 'r')
923 lines = f.readlines()
924 f.close()
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000925 except IOError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000926 lines = []
Guido van Rossum9694fca1997-10-22 21:00:49 +0000927 pat = re.escape(key) + ':(.*)\n'
928 prog = re.compile(pat, casefold and re.IGNORECASE)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000929 if value is None:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000930 newline = None
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000931 else:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000932 newline = '%s: %s\n' % (key, value)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000933 for i in range(len(lines)):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000934 line = lines[i]
935 if prog.match(line):
936 if newline is None:
937 del lines[i]
938 else:
939 lines[i] = newline
940 break
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000941 else:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000942 if newline is not None:
943 lines.append(newline)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000944 tempfile = file + "~"
945 f = open(tempfile, 'w')
946 for line in lines:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000947 f.write(line)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000948 f.close()
949 os.rename(tempfile, file)
Guido van Rossum56013131994-06-23 12:06:02 +0000950
951
952# Test program
953
954def test():
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000955 global mh, f
956 os.system('rm -rf $HOME/Mail/@test')
957 mh = MH()
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000958 def do(s): print(s); print(eval(s))
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000959 do('mh.listfolders()')
960 do('mh.listallfolders()')
961 testfolders = ['@test', '@test/test1', '@test/test2',
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000962 '@test/test1/test11', '@test/test1/test12',
963 '@test/test1/test11/test111']
Walter Dörwald70a6b492004-02-12 17:35:32 +0000964 for t in testfolders: do('mh.makefolder(%r)' % (t,))
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000965 do('mh.listsubfolders(\'@test\')')
966 do('mh.listallsubfolders(\'@test\')')
967 f = mh.openfolder('@test')
968 do('f.listsubfolders()')
969 do('f.listallsubfolders()')
970 do('f.getsequences()')
971 seqs = f.getsequences()
972 seqs['foo'] = IntSet('1-10 12-20', ' ').tolist()
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000973 print(seqs)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000974 f.putsequences(seqs)
975 do('f.getsequences()')
Walter Dörwald70a6b492004-02-12 17:35:32 +0000976 for t in reversed(testfolders): do('mh.deletefolder(%r)' % (t,))
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000977 do('mh.getcontext()')
978 context = mh.getcontext()
979 f = mh.openfolder(context)
980 do('f.getcurrent()')
Raymond Hettingerdbecd932005-02-06 06:57:08 +0000981 for seq in ('first', 'last', 'cur', '.', 'prev', 'next',
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000982 'first:3', 'last:3', 'cur:3', 'cur:-3',
983 'prev:3', 'next:3',
984 '1:3', '1:-3', '100:3', '100:-3', '10000:3', '10000:-3',
Raymond Hettingerdbecd932005-02-06 06:57:08 +0000985 'all'):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000986 try:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000987 do('f.parsesequence(%r)' % (seq,))
Guido van Rossumb940e112007-01-10 16:19:56 +0000988 except Error as msg:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000989 print("Error:", msg)
Walter Dörwald70a6b492004-02-12 17:35:32 +0000990 stuff = os.popen("pick %r 2>/dev/null" % (seq,)).read()
Eric S. Raymond66d99192001-02-09 09:19:27 +0000991 list = map(int, stuff.split())
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000992 print(list, "<-- pick")
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000993 do('f.listmessages()')
Guido van Rossum56013131994-06-23 12:06:02 +0000994
995
996if __name__ == '__main__':
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000997 test()