Initial commit of OpenSPARC T2 architecture model.
[OpenSPARC-T2-SAM] / sam-t2 / devtools / v9 / lib / python2.4 / mhlib.py
CommitLineData
920dae64
AT
1"""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
56# XXX To do, functionality:
57# - annotate messages
58# - send messages
59#
60# XXX To do, organization:
61# - move IntSet to separate file
62# - move most Message functionality to module mimetools
63
64
65# Customizable defaults
66
67MH_PROFILE = '~/.mh_profile'
68PATH = '~/Mail'
69MH_SEQUENCES = '.mh_sequences'
70FOLDER_PROTECT = 0700
71
72
73# Imported modules
74
75import os
76import sys
77import re
78import mimetools
79import multifile
80import shutil
81from bisect import bisect
82
83__all__ = ["MH","Error","Folder","Message"]
84
85# Exported constants
86
87class Error(Exception):
88 pass
89
90
91class MH:
92 """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."""
97
98 def __init__(self, path = None, profile = None):
99 """Constructor."""
100 if profile is None: profile = MH_PROFILE
101 self.profile = os.path.expanduser(profile)
102 if path is None: path = self.getprofile('Path')
103 if not path: path = PATH
104 if not os.path.isabs(path) and path[0] != '~':
105 path = os.path.join('~', path)
106 path = os.path.expanduser(path)
107 if not os.path.isdir(path): raise Error, 'MH() path not found'
108 self.path = path
109
110 def __repr__(self):
111 """String representation."""
112 return 'MH(%r, %r)' % (self.path, self.profile)
113
114 def error(self, msg, *args):
115 """Routine to print an error. May be overridden by a derived class."""
116 sys.stderr.write('MH error: %s\n' % (msg % args))
117
118 def getprofile(self, key):
119 """Return a profile entry, None if not found."""
120 return pickline(self.profile, key)
121
122 def getpath(self):
123 """Return the path (the name of the collection's directory)."""
124 return self.path
125
126 def getcontext(self):
127 """Return the name of the current folder."""
128 context = pickline(os.path.join(self.getpath(), 'context'),
129 'Current-Folder')
130 if not context: context = 'inbox'
131 return context
132
133 def setcontext(self, context):
134 """Set the name of the current folder."""
135 fn = os.path.join(self.getpath(), 'context')
136 f = open(fn, "w")
137 f.write("Current-Folder: %s\n" % context)
138 f.close()
139
140 def listfolders(self):
141 """Return the names of the top-level folders."""
142 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
150
151 def listsubfolders(self, name):
152 """Return the names of the subfolders in a given folder
153 (prefixed with the given folder name)."""
154 fullname = os.path.join(self.path, name)
155 # Get the link count so we can avoid listing folders
156 # that have no subfolders.
157 nlinks = os.stat(fullname).st_nlink
158 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
174
175 def listallfolders(self):
176 """Return the names of all folders and subfolders, recursively."""
177 return self.listallsubfolders('')
178
179 def listallsubfolders(self, name):
180 """Return the names of subfolders in a given folder, recursively."""
181 fullname = os.path.join(self.path, name)
182 # Get the link count so we can avoid listing folders
183 # that have no subfolders.
184 nlinks = os.stat(fullname).st_nlink
185 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
206
207 def openfolder(self, name):
208 """Return a new Folder object for the named folder."""
209 return Folder(self, name)
210
211 def makefolder(self, name):
212 """Create a new folder (or raise os.error if it cannot be created)."""
213 protect = pickline(self.profile, 'Folder-Protect')
214 if protect and isnumeric(protect):
215 mode = int(protect, 8)
216 else:
217 mode = FOLDER_PROTECT
218 os.mkdir(os.path.join(self.getpath(), name), mode)
219
220 def deletefolder(self, name):
221 """Delete a folder. This removes files in the folder but not
222 subdirectories. Raise os.error if deleting the folder itself fails."""
223 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)
232
233
234numericprog = re.compile('^[1-9][0-9]*$')
235def isnumeric(str):
236 return numericprog.match(str) is not None
237
238class Folder:
239 """Class representing a particular folder."""
240
241 def __init__(self, mh, name):
242 """Constructor."""
243 self.mh = mh
244 self.name = name
245 if not os.path.isdir(self.getfullname()):
246 raise Error, 'no folder %s' % name
247
248 def __repr__(self):
249 """String representation."""
250 return 'Folder(%r, %r)' % (self.mh, self.name)
251
252 def error(self, *args):
253 """Error message handler."""
254 self.mh.error(*args)
255
256 def getfullname(self):
257 """Return the full pathname of the folder."""
258 return os.path.join(self.mh.path, self.name)
259
260 def getsequencesfilename(self):
261 """Return the full pathname of the folder's sequences file."""
262 return os.path.join(self.getfullname(), MH_SEQUENCES)
263
264 def getmessagefilename(self, n):
265 """Return the full pathname of a message in the folder."""
266 return os.path.join(self.getfullname(), str(n))
267
268 def listsubfolders(self):
269 """Return list of direct subfolders."""
270 return self.mh.listsubfolders(self.name)
271
272 def listallsubfolders(self):
273 """Return list of all subfolders."""
274 return self.mh.listallsubfolders(self.name)
275
276 def listmessages(self):
277 """Return the list of messages currently present in the folder.
278 As a side effect, set self.last to the last message (or 0)."""
279 messages = []
280 match = numericprog.match
281 append = messages.append
282 for name in os.listdir(self.getfullname()):
283 if match(name):
284 append(name)
285 messages = map(int, messages)
286 messages.sort()
287 if messages:
288 self.last = messages[-1]
289 else:
290 self.last = 0
291 return messages
292
293 def getsequences(self):
294 """Return the set of sequences for the folder."""
295 sequences = {}
296 fullname = self.getsequencesfilename()
297 try:
298 f = open(fullname, 'r')
299 except IOError:
300 return sequences
301 while 1:
302 line = f.readline()
303 if not line: break
304 fields = line.split(':')
305 if len(fields) != 2:
306 self.error('bad sequence in %s: %s' %
307 (fullname, line.strip()))
308 key = fields[0].strip()
309 value = IntSet(fields[1].strip(), ' ').tolist()
310 sequences[key] = value
311 return sequences
312
313 def putsequences(self, sequences):
314 """Write the set of sequences back to the folder."""
315 fullname = self.getsequencesfilename()
316 f = None
317 for key, seq in sequences.iteritems():
318 s = IntSet('', ' ')
319 s.fromlist(seq)
320 if not f: f = open(fullname, 'w')
321 f.write('%s: %s\n' % (key, s.tostring()))
322 if not f:
323 try:
324 os.unlink(fullname)
325 except os.error:
326 pass
327 else:
328 f.close()
329
330 def getcurrent(self):
331 """Return the current message. Raise Error when there is none."""
332 seqs = self.getsequences()
333 try:
334 return max(seqs['cur'])
335 except (ValueError, KeyError):
336 raise Error, "no cur message"
337
338 def setcurrent(self, n):
339 """Set the current message."""
340 updateline(self.getsequencesfilename(), 'cur', str(n), 0)
341
342 def parsesequence(self, seq):
343 """Parse an MH sequence specification into a message list.
344 Attempt to mimic mh-sequence(5) as close as possible.
345 Also attempt to mimic observed behavior regarding which
346 conditions cause which error messages."""
347 # XXX Still not complete (see mh-format(5)).
348 # Missing are:
349 # - 'prev', 'next' as count
350 # - Sequence-Negation option
351 all = self.listmessages()
352 # Observed behavior: test for empty folder is done first
353 if not all:
354 raise Error, "no messages in %s" % self.name
355 # Common case first: all is frequently the default
356 if seq == 'all':
357 return all
358 # Test for X:Y before X-Y because 'seq:-n' matches both
359 i = seq.find(':')
360 if i >= 0:
361 head, dir, tail = seq[:i], '', seq[i+1:]
362 if tail[:1] in '-+':
363 dir, tail = tail[:1], tail[1:]
364 if not isnumeric(tail):
365 raise Error, "bad message list %s" % seq
366 try:
367 count = int(tail)
368 except (ValueError, OverflowError):
369 # Can't use sys.maxint because of i+count below
370 count = len(all)
371 try:
372 anchor = self._parseindex(head, all)
373 except Error, msg:
374 seqs = self.getsequences()
375 if not head in seqs:
376 if not msg:
377 msg = "bad message list %s" % seq
378 raise Error, msg, sys.exc_info()[2]
379 msgs = seqs[head]
380 if not msgs:
381 raise Error, "sequence %s empty" % head
382 if dir == '-':
383 return msgs[-count:]
384 else:
385 return msgs[:count]
386 else:
387 if not dir:
388 if head in ('prev', 'last'):
389 dir = '-'
390 if dir == '-':
391 i = bisect(all, anchor)
392 return all[max(0, i-count):i]
393 else:
394 i = bisect(all, anchor-1)
395 return all[i:i+count]
396 # Test for X-Y next
397 i = seq.find('-')
398 if i >= 0:
399 begin = self._parseindex(seq[:i], all)
400 end = self._parseindex(seq[i+1:], all)
401 i = bisect(all, begin-1)
402 j = bisect(all, end)
403 r = all[i:j]
404 if not r:
405 raise Error, "bad message list %s" % seq
406 return r
407 # Neither X:Y nor X-Y; must be a number or a (pseudo-)sequence
408 try:
409 n = self._parseindex(seq, all)
410 except Error, msg:
411 seqs = self.getsequences()
412 if not seq in seqs:
413 if not msg:
414 msg = "bad message list %s" % seq
415 raise Error, msg
416 return seqs[seq]
417 else:
418 if n not in all:
419 if isnumeric(seq):
420 raise Error, "message %d doesn't exist" % n
421 else:
422 raise Error, "no %s message" % seq
423 else:
424 return [n]
425
426 def _parseindex(self, seq, all):
427 """Internal: parse a message number (or cur, first, etc.)."""
428 if isnumeric(seq):
429 try:
430 return int(seq)
431 except (OverflowError, ValueError):
432 return sys.maxint
433 if seq in ('cur', '.'):
434 return self.getcurrent()
435 if seq == 'first':
436 return all[0]
437 if seq == 'last':
438 return all[-1]
439 if seq == 'next':
440 n = self.getcurrent()
441 i = bisect(all, n)
442 try:
443 return all[i]
444 except IndexError:
445 raise Error, "no next message"
446 if seq == 'prev':
447 n = self.getcurrent()
448 i = bisect(all, n-1)
449 if i == 0:
450 raise Error, "no prev message"
451 try:
452 return all[i-1]
453 except IndexError:
454 raise Error, "no prev message"
455 raise Error, None
456
457 def openmessage(self, n):
458 """Open a message -- returns a Message object."""
459 return Message(self, n)
460
461 def removemessages(self, list):
462 """Remove one or more messages -- may raise os.error."""
463 errors = []
464 deleted = []
465 for n in list:
466 path = self.getmessagefilename(n)
467 commapath = self.getmessagefilename(',' + str(n))
468 try:
469 os.unlink(commapath)
470 except os.error:
471 pass
472 try:
473 os.rename(path, commapath)
474 except os.error, msg:
475 errors.append(msg)
476 else:
477 deleted.append(n)
478 if deleted:
479 self.removefromallsequences(deleted)
480 if errors:
481 if len(errors) == 1:
482 raise os.error, errors[0]
483 else:
484 raise os.error, ('multiple errors:', errors)
485
486 def refilemessages(self, list, tofolder, keepsequences=0):
487 """Refile one or more messages -- may raise os.error.
488 'tofolder' is an open folder object."""
489 errors = []
490 refiled = {}
491 for n in list:
492 ton = tofolder.getlast() + 1
493 path = self.getmessagefilename(n)
494 topath = tofolder.getmessagefilename(ton)
495 try:
496 os.rename(path, topath)
497 except os.error:
498 # Try copying
499 try:
500 shutil.copy2(path, topath)
501 os.unlink(path)
502 except (IOError, os.error), msg:
503 errors.append(msg)
504 try:
505 os.unlink(topath)
506 except os.error:
507 pass
508 continue
509 tofolder.setlast(ton)
510 refiled[n] = ton
511 if refiled:
512 if keepsequences:
513 tofolder._copysequences(self, refiled.items())
514 self.removefromallsequences(refiled.keys())
515 if errors:
516 if len(errors) == 1:
517 raise os.error, errors[0]
518 else:
519 raise os.error, ('multiple errors:', errors)
520
521 def _copysequences(self, fromfolder, refileditems):
522 """Helper for refilemessages() to copy sequences."""
523 fromsequences = fromfolder.getsequences()
524 tosequences = self.getsequences()
525 changed = 0
526 for name, seq in fromsequences.items():
527 try:
528 toseq = tosequences[name]
529 new = 0
530 except KeyError:
531 toseq = []
532 new = 1
533 for fromn, ton in refileditems:
534 if fromn in seq:
535 toseq.append(ton)
536 changed = 1
537 if new and toseq:
538 tosequences[name] = toseq
539 if changed:
540 self.putsequences(tosequences)
541
542 def movemessage(self, n, tofolder, ton):
543 """Move one message over a specific destination message,
544 which may or may not already exist."""
545 path = self.getmessagefilename(n)
546 # Open it to check that it exists
547 f = open(path)
548 f.close()
549 del f
550 topath = tofolder.getmessagefilename(ton)
551 backuptopath = tofolder.getmessagefilename(',%d' % ton)
552 try:
553 os.rename(topath, backuptopath)
554 except os.error:
555 pass
556 try:
557 os.rename(path, topath)
558 except os.error:
559 # Try copying
560 ok = 0
561 try:
562 tofolder.setlast(None)
563 shutil.copy2(path, topath)
564 ok = 1
565 finally:
566 if not ok:
567 try:
568 os.unlink(topath)
569 except os.error:
570 pass
571 os.unlink(path)
572 self.removefromallsequences([n])
573
574 def copymessage(self, n, tofolder, ton):
575 """Copy one message over a specific destination message,
576 which may or may not already exist."""
577 path = self.getmessagefilename(n)
578 # Open it to check that it exists
579 f = open(path)
580 f.close()
581 del f
582 topath = tofolder.getmessagefilename(ton)
583 backuptopath = tofolder.getmessagefilename(',%d' % ton)
584 try:
585 os.rename(topath, backuptopath)
586 except os.error:
587 pass
588 ok = 0
589 try:
590 tofolder.setlast(None)
591 shutil.copy2(path, topath)
592 ok = 1
593 finally:
594 if not ok:
595 try:
596 os.unlink(topath)
597 except os.error:
598 pass
599
600 def createmessage(self, n, txt):
601 """Create a message, with text from the open file txt."""
602 path = self.getmessagefilename(n)
603 backuppath = self.getmessagefilename(',%d' % n)
604 try:
605 os.rename(path, backuppath)
606 except os.error:
607 pass
608 ok = 0
609 BUFSIZE = 16*1024
610 try:
611 f = open(path, "w")
612 while 1:
613 buf = txt.read(BUFSIZE)
614 if not buf:
615 break
616 f.write(buf)
617 f.close()
618 ok = 1
619 finally:
620 if not ok:
621 try:
622 os.unlink(path)
623 except os.error:
624 pass
625
626 def removefromallsequences(self, list):
627 """Remove one or more messages from all sequences (including last)
628 -- but not from 'cur'!!!"""
629 if hasattr(self, 'last') and self.last in list:
630 del self.last
631 sequences = self.getsequences()
632 changed = 0
633 for name, seq in sequences.items():
634 if name == 'cur':
635 continue
636 for n in list:
637 if n in seq:
638 seq.remove(n)
639 changed = 1
640 if not seq:
641 del sequences[name]
642 if changed:
643 self.putsequences(sequences)
644
645 def getlast(self):
646 """Return the last message number."""
647 if not hasattr(self, 'last'):
648 self.listmessages() # Set self.last
649 return self.last
650
651 def setlast(self, last):
652 """Set the last message number."""
653 if last is None:
654 if hasattr(self, 'last'):
655 del self.last
656 else:
657 self.last = last
658
659class Message(mimetools.Message):
660
661 def __init__(self, f, n, fp = None):
662 """Constructor."""
663 self.folder = f
664 self.number = n
665 if fp is None:
666 path = f.getmessagefilename(n)
667 fp = open(path, 'r')
668 mimetools.Message.__init__(self, fp)
669
670 def __repr__(self):
671 """String representation."""
672 return 'Message(%s, %s)' % (repr(self.folder), self.number)
673
674 def getheadertext(self, pred = None):
675 """Return the message's header text as a string. If an
676 argument is specified, it is used as a filter predicate to
677 decide which headers to return (its argument is the header
678 name converted to lower case)."""
679 if pred is None:
680 return ''.join(self.headers)
681 headers = []
682 hit = 0
683 for line in self.headers:
684 if not line[0].isspace():
685 i = line.find(':')
686 if i > 0:
687 hit = pred(line[:i].lower())
688 if hit: headers.append(line)
689 return ''.join(headers)
690
691 def getbodytext(self, decode = 1):
692 """Return the message's body text as string. This undoes a
693 Content-Transfer-Encoding, but does not interpret other MIME
694 features (e.g. multipart messages). To suppress decoding,
695 pass 0 as an argument."""
696 self.fp.seek(self.startofbody)
697 encoding = self.getencoding()
698 if not decode or encoding in ('', '7bit', '8bit', 'binary'):
699 return self.fp.read()
700 from StringIO import StringIO
701 output = StringIO()
702 mimetools.decode(self.fp, output, encoding)
703 return output.getvalue()
704
705 def getbodyparts(self):
706 """Only for multipart messages: return the message's body as a
707 list of SubMessage objects. Each submessage object behaves
708 (almost) as a Message object."""
709 if self.getmaintype() != 'multipart':
710 raise Error, 'Content-Type is not multipart/*'
711 bdry = self.getparam('boundary')
712 if not bdry:
713 raise Error, 'multipart/* without boundary param'
714 self.fp.seek(self.startofbody)
715 mf = multifile.MultiFile(self.fp)
716 mf.push(bdry)
717 parts = []
718 while mf.next():
719 n = "%s.%r" % (self.number, 1 + len(parts))
720 part = SubMessage(self.folder, n, mf)
721 parts.append(part)
722 mf.pop()
723 return parts
724
725 def getbody(self):
726 """Return body, either a string or a list of messages."""
727 if self.getmaintype() == 'multipart':
728 return self.getbodyparts()
729 else:
730 return self.getbodytext()
731
732
733class SubMessage(Message):
734
735 def __init__(self, f, n, fp):
736 """Constructor."""
737 Message.__init__(self, f, n, fp)
738 if self.getmaintype() == 'multipart':
739 self.body = Message.getbodyparts(self)
740 else:
741 self.body = Message.getbodytext(self)
742 self.bodyencoded = Message.getbodytext(self, decode=0)
743 # XXX If this is big, should remember file pointers
744
745 def __repr__(self):
746 """String representation."""
747 f, n, fp = self.folder, self.number, self.fp
748 return 'SubMessage(%s, %s, %s)' % (f, n, fp)
749
750 def getbodytext(self, decode = 1):
751 if not decode:
752 return self.bodyencoded
753 if type(self.body) == type(''):
754 return self.body
755
756 def getbodyparts(self):
757 if type(self.body) == type([]):
758 return self.body
759
760 def getbody(self):
761 return self.body
762
763
764class IntSet:
765 """Class implementing sets of integers.
766
767 This is an efficient representation for sets consisting of several
768 continuous ranges, e.g. 1-100,200-400,402-1000 is represented
769 internally as a list of three pairs: [(1,100), (200,400),
770 (402,1000)]. The internal representation is always kept normalized.
771
772 The constructor has up to three arguments:
773 - the string used to initialize the set (default ''),
774 - the separator between ranges (default ',')
775 - the separator between begin and end of a range (default '-')
776 The separators must be strings (not regexprs) and should be different.
777
778 The tostring() function yields a string that can be passed to another
779 IntSet constructor; __repr__() is a valid IntSet constructor itself.
780 """
781
782 # XXX The default begin/end separator means that negative numbers are
783 # not supported very well.
784 #
785 # XXX There are currently no operations to remove set elements.
786
787 def __init__(self, data = None, sep = ',', rng = '-'):
788 self.pairs = []
789 self.sep = sep
790 self.rng = rng
791 if data: self.fromstring(data)
792
793 def reset(self):
794 self.pairs = []
795
796 def __cmp__(self, other):
797 return cmp(self.pairs, other.pairs)
798
799 def __hash__(self):
800 return hash(self.pairs)
801
802 def __repr__(self):
803 return 'IntSet(%r, %r, %r)' % (self.tostring(), self.sep, self.rng)
804
805 def normalize(self):
806 self.pairs.sort()
807 i = 1
808 while i < len(self.pairs):
809 alo, ahi = self.pairs[i-1]
810 blo, bhi = self.pairs[i]
811 if ahi >= blo-1:
812 self.pairs[i-1:i+1] = [(alo, max(ahi, bhi))]
813 else:
814 i = i+1
815
816 def tostring(self):
817 s = ''
818 for lo, hi in self.pairs:
819 if lo == hi: t = repr(lo)
820 else: t = repr(lo) + self.rng + repr(hi)
821 if s: s = s + (self.sep + t)
822 else: s = t
823 return s
824
825 def tolist(self):
826 l = []
827 for lo, hi in self.pairs:
828 m = range(lo, hi+1)
829 l = l + m
830 return l
831
832 def fromlist(self, list):
833 for i in list:
834 self.append(i)
835
836 def clone(self):
837 new = IntSet()
838 new.pairs = self.pairs[:]
839 return new
840
841 def min(self):
842 return self.pairs[0][0]
843
844 def max(self):
845 return self.pairs[-1][-1]
846
847 def contains(self, x):
848 for lo, hi in self.pairs:
849 if lo <= x <= hi: return True
850 return False
851
852 def append(self, x):
853 for i in range(len(self.pairs)):
854 lo, hi = self.pairs[i]
855 if x < lo: # Need to insert before
856 if x+1 == lo:
857 self.pairs[i] = (x, hi)
858 else:
859 self.pairs.insert(i, (x, x))
860 if i > 0 and x-1 == self.pairs[i-1][1]:
861 # Merge with previous
862 self.pairs[i-1:i+1] = [
863 (self.pairs[i-1][0],
864 self.pairs[i][1])
865 ]
866 return
867 if x <= hi: # Already in set
868 return
869 i = len(self.pairs) - 1
870 if i >= 0:
871 lo, hi = self.pairs[i]
872 if x-1 == hi:
873 self.pairs[i] = lo, x
874 return
875 self.pairs.append((x, x))
876
877 def addpair(self, xlo, xhi):
878 if xlo > xhi: return
879 self.pairs.append((xlo, xhi))
880 self.normalize()
881
882 def fromstring(self, data):
883 new = []
884 for part in data.split(self.sep):
885 list = []
886 for subp in part.split(self.rng):
887 s = subp.strip()
888 list.append(int(s))
889 if len(list) == 1:
890 new.append((list[0], list[0]))
891 elif len(list) == 2 and list[0] <= list[1]:
892 new.append((list[0], list[1]))
893 else:
894 raise ValueError, 'bad data passed to IntSet'
895 self.pairs = self.pairs + new
896 self.normalize()
897
898
899# Subroutines to read/write entries in .mh_profile and .mh_sequences
900
901def pickline(file, key, casefold = 1):
902 try:
903 f = open(file, 'r')
904 except IOError:
905 return None
906 pat = re.escape(key) + ':'
907 prog = re.compile(pat, casefold and re.IGNORECASE)
908 while 1:
909 line = f.readline()
910 if not line: break
911 if prog.match(line):
912 text = line[len(key)+1:]
913 while 1:
914 line = f.readline()
915 if not line or not line[0].isspace():
916 break
917 text = text + line
918 return text.strip()
919 return None
920
921def updateline(file, key, value, casefold = 1):
922 try:
923 f = open(file, 'r')
924 lines = f.readlines()
925 f.close()
926 except IOError:
927 lines = []
928 pat = re.escape(key) + ':(.*)\n'
929 prog = re.compile(pat, casefold and re.IGNORECASE)
930 if value is None:
931 newline = None
932 else:
933 newline = '%s: %s\n' % (key, value)
934 for i in range(len(lines)):
935 line = lines[i]
936 if prog.match(line):
937 if newline is None:
938 del lines[i]
939 else:
940 lines[i] = newline
941 break
942 else:
943 if newline is not None:
944 lines.append(newline)
945 tempfile = file + "~"
946 f = open(tempfile, 'w')
947 for line in lines:
948 f.write(line)
949 f.close()
950 os.rename(tempfile, file)
951
952
953# Test program
954
955def test():
956 global mh, f
957 os.system('rm -rf $HOME/Mail/@test')
958 mh = MH()
959 def do(s): print s; print eval(s)
960 do('mh.listfolders()')
961 do('mh.listallfolders()')
962 testfolders = ['@test', '@test/test1', '@test/test2',
963 '@test/test1/test11', '@test/test1/test12',
964 '@test/test1/test11/test111']
965 for t in testfolders: do('mh.makefolder(%r)' % (t,))
966 do('mh.listsubfolders(\'@test\')')
967 do('mh.listallsubfolders(\'@test\')')
968 f = mh.openfolder('@test')
969 do('f.listsubfolders()')
970 do('f.listallsubfolders()')
971 do('f.getsequences()')
972 seqs = f.getsequences()
973 seqs['foo'] = IntSet('1-10 12-20', ' ').tolist()
974 print seqs
975 f.putsequences(seqs)
976 do('f.getsequences()')
977 for t in reversed(testfolders): do('mh.deletefolder(%r)' % (t,))
978 do('mh.getcontext()')
979 context = mh.getcontext()
980 f = mh.openfolder(context)
981 do('f.getcurrent()')
982 for seq in ['first', 'last', 'cur', '.', 'prev', 'next',
983 'first:3', 'last:3', 'cur:3', 'cur:-3',
984 'prev:3', 'next:3',
985 '1:3', '1:-3', '100:3', '100:-3', '10000:3', '10000:-3',
986 'all']:
987 try:
988 do('f.parsesequence(%r)' % (seq,))
989 except Error, msg:
990 print "Error:", msg
991 stuff = os.popen("pick %r 2>/dev/null" % (seq,)).read()
992 list = map(int, stuff.split())
993 print list, "<-- pick"
994 do('f.listmessages()')
995
996
997if __name__ == '__main__':
998 test()