PyXR

c:\python24\lib \ mhlib.py



0001 """MH interface -- purely object-oriented (well, almost)
0002 
0003 Executive summary:
0004 
0005 import mhlib
0006 
0007 mh = mhlib.MH()         # use default mailbox directory and profile
0008 mh = mhlib.MH(mailbox)  # override mailbox location (default from profile)
0009 mh = mhlib.MH(mailbox, profile) # override mailbox and profile
0010 
0011 mh.error(format, ...)   # print error message -- can be overridden
0012 s = mh.getprofile(key)  # profile entry (None if not set)
0013 path = mh.getpath()     # mailbox pathname
0014 name = mh.getcontext()  # name of current folder
0015 mh.setcontext(name)     # set name of current folder
0016 
0017 list = mh.listfolders() # names of top-level folders
0018 list = mh.listallfolders() # names of all folders, including subfolders
0019 list = mh.listsubfolders(name) # direct subfolders of given folder
0020 list = mh.listallsubfolders(name) # all subfolders of given folder
0021 
0022 mh.makefolder(name)     # create new folder
0023 mh.deletefolder(name)   # delete folder -- must have no subfolders
0024 
0025 f = mh.openfolder(name) # new open folder object
0026 
0027 f.error(format, ...)    # same as mh.error(format, ...)
0028 path = f.getfullname()  # folder's full pathname
0029 path = f.getsequencesfilename() # full pathname of folder's sequences file
0030 path = f.getmessagefilename(n)  # full pathname of message n in folder
0031 
0032 list = f.listmessages() # list of messages in folder (as numbers)
0033 n = f.getcurrent()      # get current message
0034 f.setcurrent(n)         # set current message
0035 list = f.parsesequence(seq)     # parse msgs syntax into list of messages
0036 n = f.getlast()         # get last message (0 if no messagse)
0037 f.setlast(n)            # set last message (internal use only)
0038 
0039 dict = f.getsequences() # dictionary of sequences in folder {name: list}
0040 f.putsequences(dict)    # write sequences back to folder
0041 
0042 f.createmessage(n, fp)  # add message from file f as number n
0043 f.removemessages(list)  # remove messages in list from folder
0044 f.refilemessages(list, tofolder) # move messages in list to other folder
0045 f.movemessage(n, tofolder, ton)  # move one message to a given destination
0046 f.copymessage(n, tofolder, ton)  # copy one message to a given destination
0047 
0048 m = f.openmessage(n)    # new open message object (costs a file descriptor)
0049 m is a derived class of mimetools.Message(rfc822.Message), with:
0050 s = m.getheadertext()   # text of message's headers
0051 s = m.getheadertext(pred) # text of message's headers, filtered by pred
0052 s = m.getbodytext()     # text of message's body, decoded
0053 s = m.getbodytext(0)    # text of message's body, not decoded
0054 """
0055 
0056 # XXX To do, functionality:
0057 # - annotate messages
0058 # - send messages
0059 #
0060 # XXX To do, organization:
0061 # - move IntSet to separate file
0062 # - move most Message functionality to module mimetools
0063 
0064 
0065 # Customizable defaults
0066 
0067 MH_PROFILE = '~/.mh_profile'
0068 PATH = '~/Mail'
0069 MH_SEQUENCES = '.mh_sequences'
0070 FOLDER_PROTECT = 0700
0071 
0072 
0073 # Imported modules
0074 
0075 import os
0076 import sys
0077 import re
0078 import mimetools
0079 import multifile
0080 import shutil
0081 from bisect import bisect
0082 
0083 __all__ = ["MH","Error","Folder","Message"]
0084 
0085 # Exported constants
0086 
0087 class Error(Exception):
0088     pass
0089 
0090 
0091 class MH:
0092     """Class representing a particular collection of folders.
0093     Optional constructor arguments are the pathname for the directory
0094     containing the collection, and the MH profile to use.
0095     If either is omitted or empty a default is used; the default
0096     directory is taken from the MH profile if it is specified there."""
0097 
0098     def __init__(self, path = None, profile = None):
0099         """Constructor."""
0100         if profile is None: profile = MH_PROFILE
0101         self.profile = os.path.expanduser(profile)
0102         if path is None: path = self.getprofile('Path')
0103         if not path: path = PATH
0104         if not os.path.isabs(path) and path[0] != '~':
0105             path = os.path.join('~', path)
0106         path = os.path.expanduser(path)
0107         if not os.path.isdir(path): raise Error, 'MH() path not found'
0108         self.path = path
0109 
0110     def __repr__(self):
0111         """String representation."""
0112         return 'MH(%r, %r)' % (self.path, self.profile)
0113 
0114     def error(self, msg, *args):
0115         """Routine to print an error.  May be overridden by a derived class."""
0116         sys.stderr.write('MH error: %s\n' % (msg % args))
0117 
0118     def getprofile(self, key):
0119         """Return a profile entry, None if not found."""
0120         return pickline(self.profile, key)
0121 
0122     def getpath(self):
0123         """Return the path (the name of the collection's directory)."""
0124         return self.path
0125 
0126     def getcontext(self):
0127         """Return the name of the current folder."""
0128         context = pickline(os.path.join(self.getpath(), 'context'),
0129                   'Current-Folder')
0130         if not context: context = 'inbox'
0131         return context
0132 
0133     def setcontext(self, context):
0134         """Set the name of the current folder."""
0135         fn = os.path.join(self.getpath(), 'context')
0136         f = open(fn, "w")
0137         f.write("Current-Folder: %s\n" % context)
0138         f.close()
0139 
0140     def listfolders(self):
0141         """Return the names of the top-level folders."""
0142         folders = []
0143         path = self.getpath()
0144         for name in os.listdir(path):
0145             fullname = os.path.join(path, name)
0146             if os.path.isdir(fullname):
0147                 folders.append(name)
0148         folders.sort()
0149         return folders
0150 
0151     def listsubfolders(self, name):
0152         """Return the names of the subfolders in a given folder
0153         (prefixed with the given folder name)."""
0154         fullname = os.path.join(self.path, name)
0155         # Get the link count so we can avoid listing folders
0156         # that have no subfolders.
0157         nlinks = os.stat(fullname).st_nlink
0158         if nlinks <= 2:
0159             return []
0160         subfolders = []
0161         subnames = os.listdir(fullname)
0162         for subname in subnames:
0163             fullsubname = os.path.join(fullname, subname)
0164             if os.path.isdir(fullsubname):
0165                 name_subname = os.path.join(name, subname)
0166                 subfolders.append(name_subname)
0167                 # Stop looking for subfolders when
0168                 # we've seen them all
0169                 nlinks = nlinks - 1
0170                 if nlinks <= 2:
0171                     break
0172         subfolders.sort()
0173         return subfolders
0174 
0175     def listallfolders(self):
0176         """Return the names of all folders and subfolders, recursively."""
0177         return self.listallsubfolders('')
0178 
0179     def listallsubfolders(self, name):
0180         """Return the names of subfolders in a given folder, recursively."""
0181         fullname = os.path.join(self.path, name)
0182         # Get the link count so we can avoid listing folders
0183         # that have no subfolders.
0184         nlinks = os.stat(fullname).st_nlink
0185         if nlinks <= 2:
0186             return []
0187         subfolders = []
0188         subnames = os.listdir(fullname)
0189         for subname in subnames:
0190             if subname[0] == ',' or isnumeric(subname): continue
0191             fullsubname = os.path.join(fullname, subname)
0192             if os.path.isdir(fullsubname):
0193                 name_subname = os.path.join(name, subname)
0194                 subfolders.append(name_subname)
0195                 if not os.path.islink(fullsubname):
0196                     subsubfolders = self.listallsubfolders(
0197                               name_subname)
0198                     subfolders = subfolders + subsubfolders
0199                 # Stop looking for subfolders when
0200                 # we've seen them all
0201                 nlinks = nlinks - 1
0202                 if nlinks <= 2:
0203                     break
0204         subfolders.sort()
0205         return subfolders
0206 
0207     def openfolder(self, name):
0208         """Return a new Folder object for the named folder."""
0209         return Folder(self, name)
0210 
0211     def makefolder(self, name):
0212         """Create a new folder (or raise os.error if it cannot be created)."""
0213         protect = pickline(self.profile, 'Folder-Protect')
0214         if protect and isnumeric(protect):
0215             mode = int(protect, 8)
0216         else:
0217             mode = FOLDER_PROTECT
0218         os.mkdir(os.path.join(self.getpath(), name), mode)
0219 
0220     def deletefolder(self, name):
0221         """Delete a folder.  This removes files in the folder but not
0222         subdirectories.  Raise os.error if deleting the folder itself fails."""
0223         fullname = os.path.join(self.getpath(), name)
0224         for subname in os.listdir(fullname):
0225             fullsubname = os.path.join(fullname, subname)
0226             try:
0227                 os.unlink(fullsubname)
0228             except os.error:
0229                 self.error('%s not deleted, continuing...' %
0230                           fullsubname)
0231         os.rmdir(fullname)
0232 
0233 
0234 numericprog = re.compile('^[1-9][0-9]*$')
0235 def isnumeric(str):
0236     return numericprog.match(str) is not None
0237 
0238 class Folder:
0239     """Class representing a particular folder."""
0240 
0241     def __init__(self, mh, name):
0242         """Constructor."""
0243         self.mh = mh
0244         self.name = name
0245         if not os.path.isdir(self.getfullname()):
0246             raise Error, 'no folder %s' % name
0247 
0248     def __repr__(self):
0249         """String representation."""
0250         return 'Folder(%r, %r)' % (self.mh, self.name)
0251 
0252     def error(self, *args):
0253         """Error message handler."""
0254         self.mh.error(*args)
0255 
0256     def getfullname(self):
0257         """Return the full pathname of the folder."""
0258         return os.path.join(self.mh.path, self.name)
0259 
0260     def getsequencesfilename(self):
0261         """Return the full pathname of the folder's sequences file."""
0262         return os.path.join(self.getfullname(), MH_SEQUENCES)
0263 
0264     def getmessagefilename(self, n):
0265         """Return the full pathname of a message in the folder."""
0266         return os.path.join(self.getfullname(), str(n))
0267 
0268     def listsubfolders(self):
0269         """Return list of direct subfolders."""
0270         return self.mh.listsubfolders(self.name)
0271 
0272     def listallsubfolders(self):
0273         """Return list of all subfolders."""
0274         return self.mh.listallsubfolders(self.name)
0275 
0276     def listmessages(self):
0277         """Return the list of messages currently present in the folder.
0278         As a side effect, set self.last to the last message (or 0)."""
0279         messages = []
0280         match = numericprog.match
0281         append = messages.append
0282         for name in os.listdir(self.getfullname()):
0283             if match(name):
0284                 append(name)
0285         messages = map(int, messages)
0286         messages.sort()
0287         if messages:
0288             self.last = messages[-1]
0289         else:
0290             self.last = 0
0291         return messages
0292 
0293     def getsequences(self):
0294         """Return the set of sequences for the folder."""
0295         sequences = {}
0296         fullname = self.getsequencesfilename()
0297         try:
0298             f = open(fullname, 'r')
0299         except IOError:
0300             return sequences
0301         while 1:
0302             line = f.readline()
0303             if not line: break
0304             fields = line.split(':')
0305             if len(fields) != 2:
0306                 self.error('bad sequence in %s: %s' %
0307                           (fullname, line.strip()))
0308             key = fields[0].strip()
0309             value = IntSet(fields[1].strip(), ' ').tolist()
0310             sequences[key] = value
0311         return sequences
0312 
0313     def putsequences(self, sequences):
0314         """Write the set of sequences back to the folder."""
0315         fullname = self.getsequencesfilename()
0316         f = None
0317         for key, seq in sequences.iteritems():
0318             s = IntSet('', ' ')
0319             s.fromlist(seq)
0320             if not f: f = open(fullname, 'w')
0321             f.write('%s: %s\n' % (key, s.tostring()))
0322         if not f:
0323             try:
0324                 os.unlink(fullname)
0325             except os.error:
0326                 pass
0327         else:
0328             f.close()
0329 
0330     def getcurrent(self):
0331         """Return the current message.  Raise Error when there is none."""
0332         seqs = self.getsequences()
0333         try:
0334             return max(seqs['cur'])
0335         except (ValueError, KeyError):
0336             raise Error, "no cur message"
0337 
0338     def setcurrent(self, n):
0339         """Set the current message."""
0340         updateline(self.getsequencesfilename(), 'cur', str(n), 0)
0341 
0342     def parsesequence(self, seq):
0343         """Parse an MH sequence specification into a message list.
0344         Attempt to mimic mh-sequence(5) as close as possible.
0345         Also attempt to mimic observed behavior regarding which
0346         conditions cause which error messages."""
0347         # XXX Still not complete (see mh-format(5)).
0348         # Missing are:
0349         # - 'prev', 'next' as count
0350         # - Sequence-Negation option
0351         all = self.listmessages()
0352         # Observed behavior: test for empty folder is done first
0353         if not all:
0354             raise Error, "no messages in %s" % self.name
0355         # Common case first: all is frequently the default
0356         if seq == 'all':
0357             return all
0358         # Test for X:Y before X-Y because 'seq:-n' matches both
0359         i = seq.find(':')
0360         if i >= 0:
0361             head, dir, tail = seq[:i], '', seq[i+1:]
0362             if tail[:1] in '-+':
0363                 dir, tail = tail[:1], tail[1:]
0364             if not isnumeric(tail):
0365                 raise Error, "bad message list %s" % seq
0366             try:
0367                 count = int(tail)
0368             except (ValueError, OverflowError):
0369                 # Can't use sys.maxint because of i+count below
0370                 count = len(all)
0371             try:
0372                 anchor = self._parseindex(head, all)
0373             except Error, msg:
0374                 seqs = self.getsequences()
0375                 if not head in seqs:
0376                     if not msg:
0377                         msg = "bad message list %s" % seq
0378                     raise Error, msg, sys.exc_info()[2]
0379                 msgs = seqs[head]
0380                 if not msgs:
0381                     raise Error, "sequence %s empty" % head
0382                 if dir == '-':
0383                     return msgs[-count:]
0384                 else:
0385                     return msgs[:count]
0386             else:
0387                 if not dir:
0388                     if head in ('prev', 'last'):
0389                         dir = '-'
0390                 if dir == '-':
0391                     i = bisect(all, anchor)
0392                     return all[max(0, i-count):i]
0393                 else:
0394                     i = bisect(all, anchor-1)
0395                     return all[i:i+count]
0396         # Test for X-Y next
0397         i = seq.find('-')
0398         if i >= 0:
0399             begin = self._parseindex(seq[:i], all)
0400             end = self._parseindex(seq[i+1:], all)
0401             i = bisect(all, begin-1)
0402             j = bisect(all, end)
0403             r = all[i:j]
0404             if not r:
0405                 raise Error, "bad message list %s" % seq
0406             return r
0407         # Neither X:Y nor X-Y; must be a number or a (pseudo-)sequence
0408         try:
0409             n = self._parseindex(seq, all)
0410         except Error, msg:
0411             seqs = self.getsequences()
0412             if not seq in seqs:
0413                 if not msg:
0414                     msg = "bad message list %s" % seq
0415                 raise Error, msg
0416             return seqs[seq]
0417         else:
0418             if n not in all:
0419                 if isnumeric(seq):
0420                     raise Error, "message %d doesn't exist" % n
0421                 else:
0422                     raise Error, "no %s message" % seq
0423             else:
0424                 return [n]
0425 
0426     def _parseindex(self, seq, all):
0427         """Internal: parse a message number (or cur, first, etc.)."""
0428         if isnumeric(seq):
0429             try:
0430                 return int(seq)
0431             except (OverflowError, ValueError):
0432                 return sys.maxint
0433         if seq in ('cur', '.'):
0434             return self.getcurrent()
0435         if seq == 'first':
0436             return all[0]
0437         if seq == 'last':
0438             return all[-1]
0439         if seq == 'next':
0440             n = self.getcurrent()
0441             i = bisect(all, n)
0442             try:
0443                 return all[i]
0444             except IndexError:
0445                 raise Error, "no next message"
0446         if seq == 'prev':
0447             n = self.getcurrent()
0448             i = bisect(all, n-1)
0449             if i == 0:
0450                 raise Error, "no prev message"
0451             try:
0452                 return all[i-1]
0453             except IndexError:
0454                 raise Error, "no prev message"
0455         raise Error, None
0456 
0457     def openmessage(self, n):
0458         """Open a message -- returns a Message object."""
0459         return Message(self, n)
0460 
0461     def removemessages(self, list):
0462         """Remove one or more messages -- may raise os.error."""
0463         errors = []
0464         deleted = []
0465         for n in list:
0466             path = self.getmessagefilename(n)
0467             commapath = self.getmessagefilename(',' + str(n))
0468             try:
0469                 os.unlink(commapath)
0470             except os.error:
0471                 pass
0472             try:
0473                 os.rename(path, commapath)
0474             except os.error, msg:
0475                 errors.append(msg)
0476             else:
0477                 deleted.append(n)
0478         if deleted:
0479             self.removefromallsequences(deleted)
0480         if errors:
0481             if len(errors) == 1:
0482                 raise os.error, errors[0]
0483             else:
0484                 raise os.error, ('multiple errors:', errors)
0485 
0486     def refilemessages(self, list, tofolder, keepsequences=0):
0487         """Refile one or more messages -- may raise os.error.
0488         'tofolder' is an open folder object."""
0489         errors = []
0490         refiled = {}
0491         for n in list:
0492             ton = tofolder.getlast() + 1
0493             path = self.getmessagefilename(n)
0494             topath = tofolder.getmessagefilename(ton)
0495             try:
0496                 os.rename(path, topath)
0497             except os.error:
0498                 # Try copying
0499                 try:
0500                     shutil.copy2(path, topath)
0501                     os.unlink(path)
0502                 except (IOError, os.error), msg:
0503                     errors.append(msg)
0504                     try:
0505                         os.unlink(topath)
0506                     except os.error:
0507                         pass
0508                     continue
0509             tofolder.setlast(ton)
0510             refiled[n] = ton
0511         if refiled:
0512             if keepsequences:
0513                 tofolder._copysequences(self, refiled.items())
0514             self.removefromallsequences(refiled.keys())
0515         if errors:
0516             if len(errors) == 1:
0517                 raise os.error, errors[0]
0518             else:
0519                 raise os.error, ('multiple errors:', errors)
0520 
0521     def _copysequences(self, fromfolder, refileditems):
0522         """Helper for refilemessages() to copy sequences."""
0523         fromsequences = fromfolder.getsequences()
0524         tosequences = self.getsequences()
0525         changed = 0
0526         for name, seq in fromsequences.items():
0527             try:
0528                 toseq = tosequences[name]
0529                 new = 0
0530             except KeyError:
0531                 toseq = []
0532                 new = 1
0533             for fromn, ton in refileditems:
0534                 if fromn in seq:
0535                     toseq.append(ton)
0536                     changed = 1
0537             if new and toseq:
0538                 tosequences[name] = toseq
0539         if changed:
0540             self.putsequences(tosequences)
0541 
0542     def movemessage(self, n, tofolder, ton):
0543         """Move one message over a specific destination message,
0544         which may or may not already exist."""
0545         path = self.getmessagefilename(n)
0546         # Open it to check that it exists
0547         f = open(path)
0548         f.close()
0549         del f
0550         topath = tofolder.getmessagefilename(ton)
0551         backuptopath = tofolder.getmessagefilename(',%d' % ton)
0552         try:
0553             os.rename(topath, backuptopath)
0554         except os.error:
0555             pass
0556         try:
0557             os.rename(path, topath)
0558         except os.error:
0559             # Try copying
0560             ok = 0
0561             try:
0562                 tofolder.setlast(None)
0563                 shutil.copy2(path, topath)
0564                 ok = 1
0565             finally:
0566                 if not ok:
0567                     try:
0568                         os.unlink(topath)
0569                     except os.error:
0570                         pass
0571             os.unlink(path)
0572         self.removefromallsequences([n])
0573 
0574     def copymessage(self, n, tofolder, ton):
0575         """Copy one message over a specific destination message,
0576         which may or may not already exist."""
0577         path = self.getmessagefilename(n)
0578         # Open it to check that it exists
0579         f = open(path)
0580         f.close()
0581         del f
0582         topath = tofolder.getmessagefilename(ton)
0583         backuptopath = tofolder.getmessagefilename(',%d' % ton)
0584         try:
0585             os.rename(topath, backuptopath)
0586         except os.error:
0587             pass
0588         ok = 0
0589         try:
0590             tofolder.setlast(None)
0591             shutil.copy2(path, topath)
0592             ok = 1
0593         finally:
0594             if not ok:
0595                 try:
0596                     os.unlink(topath)
0597                 except os.error:
0598                     pass
0599 
0600     def createmessage(self, n, txt):
0601         """Create a message, with text from the open file txt."""
0602         path = self.getmessagefilename(n)
0603         backuppath = self.getmessagefilename(',%d' % n)
0604         try:
0605             os.rename(path, backuppath)
0606         except os.error:
0607             pass
0608         ok = 0
0609         BUFSIZE = 16*1024
0610         try:
0611             f = open(path, "w")
0612             while 1:
0613                 buf = txt.read(BUFSIZE)
0614                 if not buf:
0615                     break
0616                 f.write(buf)
0617             f.close()
0618             ok = 1
0619         finally:
0620             if not ok:
0621                 try:
0622                     os.unlink(path)
0623                 except os.error:
0624                     pass
0625 
0626     def removefromallsequences(self, list):
0627         """Remove one or more messages from all sequences (including last)
0628         -- but not from 'cur'!!!"""
0629         if hasattr(self, 'last') and self.last in list:
0630             del self.last
0631         sequences = self.getsequences()
0632         changed = 0
0633         for name, seq in sequences.items():
0634             if name == 'cur':
0635                 continue
0636             for n in list:
0637                 if n in seq:
0638                     seq.remove(n)
0639                     changed = 1
0640                     if not seq:
0641                         del sequences[name]
0642         if changed:
0643             self.putsequences(sequences)
0644 
0645     def getlast(self):
0646         """Return the last message number."""
0647         if not hasattr(self, 'last'):
0648             self.listmessages() # Set self.last
0649         return self.last
0650 
0651     def setlast(self, last):
0652         """Set the last message number."""
0653         if last is None:
0654             if hasattr(self, 'last'):
0655                 del self.last
0656         else:
0657             self.last = last
0658 
0659 class Message(mimetools.Message):
0660 
0661     def __init__(self, f, n, fp = None):
0662         """Constructor."""
0663         self.folder = f
0664         self.number = n
0665         if fp is None:
0666             path = f.getmessagefilename(n)
0667             fp = open(path, 'r')
0668         mimetools.Message.__init__(self, fp)
0669 
0670     def __repr__(self):
0671         """String representation."""
0672         return 'Message(%s, %s)' % (repr(self.folder), self.number)
0673 
0674     def getheadertext(self, pred = None):
0675         """Return the message's header text as a string.  If an
0676         argument is specified, it is used as a filter predicate to
0677         decide which headers to return (its argument is the header
0678         name converted to lower case)."""
0679         if pred is None:
0680             return ''.join(self.headers)
0681         headers = []
0682         hit = 0
0683         for line in self.headers:
0684             if not line[0].isspace():
0685                 i = line.find(':')
0686                 if i > 0:
0687                     hit = pred(line[:i].lower())
0688             if hit: headers.append(line)
0689         return ''.join(headers)
0690 
0691     def getbodytext(self, decode = 1):
0692         """Return the message's body text as string.  This undoes a
0693         Content-Transfer-Encoding, but does not interpret other MIME
0694         features (e.g. multipart messages).  To suppress decoding,
0695         pass 0 as an argument."""
0696         self.fp.seek(self.startofbody)
0697         encoding = self.getencoding()
0698         if not decode or encoding in ('', '7bit', '8bit', 'binary'):
0699             return self.fp.read()
0700         from StringIO import StringIO
0701         output = StringIO()
0702         mimetools.decode(self.fp, output, encoding)
0703         return output.getvalue()
0704 
0705     def getbodyparts(self):
0706         """Only for multipart messages: return the message's body as a
0707         list of SubMessage objects.  Each submessage object behaves
0708         (almost) as a Message object."""
0709         if self.getmaintype() != 'multipart':
0710             raise Error, 'Content-Type is not multipart/*'
0711         bdry = self.getparam('boundary')
0712         if not bdry:
0713             raise Error, 'multipart/* without boundary param'
0714         self.fp.seek(self.startofbody)
0715         mf = multifile.MultiFile(self.fp)
0716         mf.push(bdry)
0717         parts = []
0718         while mf.next():
0719             n = "%s.%r" % (self.number, 1 + len(parts))
0720             part = SubMessage(self.folder, n, mf)
0721             parts.append(part)
0722         mf.pop()
0723         return parts
0724 
0725     def getbody(self):
0726         """Return body, either a string or a list of messages."""
0727         if self.getmaintype() == 'multipart':
0728             return self.getbodyparts()
0729         else:
0730             return self.getbodytext()
0731 
0732 
0733 class SubMessage(Message):
0734 
0735     def __init__(self, f, n, fp):
0736         """Constructor."""
0737         Message.__init__(self, f, n, fp)
0738         if self.getmaintype() == 'multipart':
0739             self.body = Message.getbodyparts(self)
0740         else:
0741             self.body = Message.getbodytext(self)
0742         self.bodyencoded = Message.getbodytext(self, decode=0)
0743             # XXX If this is big, should remember file pointers
0744 
0745     def __repr__(self):
0746         """String representation."""
0747         f, n, fp = self.folder, self.number, self.fp
0748         return 'SubMessage(%s, %s, %s)' % (f, n, fp)
0749 
0750     def getbodytext(self, decode = 1):
0751         if not decode:
0752             return self.bodyencoded
0753         if type(self.body) == type(''):
0754             return self.body
0755 
0756     def getbodyparts(self):
0757         if type(self.body) == type([]):
0758             return self.body
0759 
0760     def getbody(self):
0761         return self.body
0762 
0763 
0764 class IntSet:
0765     """Class implementing sets of integers.
0766 
0767     This is an efficient representation for sets consisting of several
0768     continuous ranges, e.g. 1-100,200-400,402-1000 is represented
0769     internally as a list of three pairs: [(1,100), (200,400),
0770     (402,1000)].  The internal representation is always kept normalized.
0771 
0772     The constructor has up to three arguments:
0773     - the string used to initialize the set (default ''),
0774     - the separator between ranges (default ',')
0775     - the separator between begin and end of a range (default '-')
0776     The separators must be strings (not regexprs) and should be different.
0777 
0778     The tostring() function yields a string that can be passed to another
0779     IntSet constructor; __repr__() is a valid IntSet constructor itself.
0780     """
0781 
0782     # XXX The default begin/end separator means that negative numbers are
0783     #     not supported very well.
0784     #
0785     # XXX There are currently no operations to remove set elements.
0786 
0787     def __init__(self, data = None, sep = ',', rng = '-'):
0788         self.pairs = []
0789         self.sep = sep
0790         self.rng = rng
0791         if data: self.fromstring(data)
0792 
0793     def reset(self):
0794         self.pairs = []
0795 
0796     def __cmp__(self, other):
0797         return cmp(self.pairs, other.pairs)
0798 
0799     def __hash__(self):
0800         return hash(self.pairs)
0801 
0802     def __repr__(self):
0803         return 'IntSet(%r, %r, %r)' % (self.tostring(), self.sep, self.rng)
0804 
0805     def normalize(self):
0806         self.pairs.sort()
0807         i = 1
0808         while i < len(self.pairs):
0809             alo, ahi = self.pairs[i-1]
0810             blo, bhi = self.pairs[i]
0811             if ahi >= blo-1:
0812                 self.pairs[i-1:i+1] = [(alo, max(ahi, bhi))]
0813             else:
0814                 i = i+1
0815 
0816     def tostring(self):
0817         s = ''
0818         for lo, hi in self.pairs:
0819             if lo == hi: t = repr(lo)
0820             else: t = repr(lo) + self.rng + repr(hi)
0821             if s: s = s + (self.sep + t)
0822             else: s = t
0823         return s
0824 
0825     def tolist(self):
0826         l = []
0827         for lo, hi in self.pairs:
0828             m = range(lo, hi+1)
0829             l = l + m
0830         return l
0831 
0832     def fromlist(self, list):
0833         for i in list:
0834             self.append(i)
0835 
0836     def clone(self):
0837         new = IntSet()
0838         new.pairs = self.pairs[:]
0839         return new
0840 
0841     def min(self):
0842         return self.pairs[0][0]
0843 
0844     def max(self):
0845         return self.pairs[-1][-1]
0846 
0847     def contains(self, x):
0848         for lo, hi in self.pairs:
0849             if lo <= x <= hi: return True
0850         return False
0851 
0852     def append(self, x):
0853         for i in range(len(self.pairs)):
0854             lo, hi = self.pairs[i]
0855             if x < lo: # Need to insert before
0856                 if x+1 == lo:
0857                     self.pairs[i] = (x, hi)
0858                 else:
0859                     self.pairs.insert(i, (x, x))
0860                 if i > 0 and x-1 == self.pairs[i-1][1]:
0861                     # Merge with previous
0862                     self.pairs[i-1:i+1] = [
0863                             (self.pairs[i-1][0],
0864                              self.pairs[i][1])
0865                           ]
0866                 return
0867             if x <= hi: # Already in set
0868                 return
0869         i = len(self.pairs) - 1
0870         if i >= 0:
0871             lo, hi = self.pairs[i]
0872             if x-1 == hi:
0873                 self.pairs[i] = lo, x
0874                 return
0875         self.pairs.append((x, x))
0876 
0877     def addpair(self, xlo, xhi):
0878         if xlo > xhi: return
0879         self.pairs.append((xlo, xhi))
0880         self.normalize()
0881 
0882     def fromstring(self, data):
0883         new = []
0884         for part in data.split(self.sep):
0885             list = []
0886             for subp in part.split(self.rng):
0887                 s = subp.strip()
0888                 list.append(int(s))
0889             if len(list) == 1:
0890                 new.append((list[0], list[0]))
0891             elif len(list) == 2 and list[0] <= list[1]:
0892                 new.append((list[0], list[1]))
0893             else:
0894                 raise ValueError, 'bad data passed to IntSet'
0895         self.pairs = self.pairs + new
0896         self.normalize()
0897 
0898 
0899 # Subroutines to read/write entries in .mh_profile and .mh_sequences
0900 
0901 def pickline(file, key, casefold = 1):
0902     try:
0903         f = open(file, 'r')
0904     except IOError:
0905         return None
0906     pat = re.escape(key) + ':'
0907     prog = re.compile(pat, casefold and re.IGNORECASE)
0908     while 1:
0909         line = f.readline()
0910         if not line: break
0911         if prog.match(line):
0912             text = line[len(key)+1:]
0913             while 1:
0914                 line = f.readline()
0915                 if not line or not line[0].isspace():
0916                     break
0917                 text = text + line
0918             return text.strip()
0919     return None
0920 
0921 def updateline(file, key, value, casefold = 1):
0922     try:
0923         f = open(file, 'r')
0924         lines = f.readlines()
0925         f.close()
0926     except IOError:
0927         lines = []
0928     pat = re.escape(key) + ':(.*)\n'
0929     prog = re.compile(pat, casefold and re.IGNORECASE)
0930     if value is None:
0931         newline = None
0932     else:
0933         newline = '%s: %s\n' % (key, value)
0934     for i in range(len(lines)):
0935         line = lines[i]
0936         if prog.match(line):
0937             if newline is None:
0938                 del lines[i]
0939             else:
0940                 lines[i] = newline
0941             break
0942     else:
0943         if newline is not None:
0944             lines.append(newline)
0945     tempfile = file + "~"
0946     f = open(tempfile, 'w')
0947     for line in lines:
0948         f.write(line)
0949     f.close()
0950     os.rename(tempfile, file)
0951 
0952 
0953 # Test program
0954 
0955 def test():
0956     global mh, f
0957     os.system('rm -rf $HOME/Mail/@test')
0958     mh = MH()
0959     def do(s): print s; print eval(s)
0960     do('mh.listfolders()')
0961     do('mh.listallfolders()')
0962     testfolders = ['@test', '@test/test1', '@test/test2',
0963                    '@test/test1/test11', '@test/test1/test12',
0964                    '@test/test1/test11/test111']
0965     for t in testfolders: do('mh.makefolder(%r)' % (t,))
0966     do('mh.listsubfolders(\'@test\')')
0967     do('mh.listallsubfolders(\'@test\')')
0968     f = mh.openfolder('@test')
0969     do('f.listsubfolders()')
0970     do('f.listallsubfolders()')
0971     do('f.getsequences()')
0972     seqs = f.getsequences()
0973     seqs['foo'] = IntSet('1-10 12-20', ' ').tolist()
0974     print seqs
0975     f.putsequences(seqs)
0976     do('f.getsequences()')
0977     for t in reversed(testfolders): do('mh.deletefolder(%r)' % (t,))
0978     do('mh.getcontext()')
0979     context = mh.getcontext()
0980     f = mh.openfolder(context)
0981     do('f.getcurrent()')
0982     for seq in ['first', 'last', 'cur', '.', 'prev', 'next',
0983                 'first:3', 'last:3', 'cur:3', 'cur:-3',
0984                 'prev:3', 'next:3',
0985                 '1:3', '1:-3', '100:3', '100:-3', '10000:3', '10000:-3',
0986                 'all']:
0987         try:
0988             do('f.parsesequence(%r)' % (seq,))
0989         except Error, msg:
0990             print "Error:", msg
0991         stuff = os.popen("pick %r 2>/dev/null" % (seq,)).read()
0992         list = map(int, stuff.split())
0993         print list, "<-- pick"
0994     do('f.listmessages()')
0995 
0996 
0997 if __name__ == '__main__':
0998     test()
0999 

Generated by PyXR 0.9.4
SourceForge.net Logo