home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Tools / Languages / Python 1.1 / Lib / mhlib.py < prev    next >
Encoding:
Python Source  |  1994-09-09  |  19.7 KB  |  745 lines  |  [TEXT/R*ch]

  1. # MH interface -- purely object-oriented (well, almost)
  2. #
  3. # Executive summary:
  4. #
  5. # import mhlib
  6. #
  7. # mh = mhlib.MH()         # use default mailbox directory and profile
  8. # mh = mhlib.MH(mailbox)  # override mailbox location (default from profile)
  9. # mh = mhlib.MH(mailbox, profile) # override mailbox and profile
  10. #
  11. # mh.error(format, ...)   # print error message -- can be overridden
  12. # s = mh.getprofile(key)  # profile entry (None if not set)
  13. # path = mh.getpath()     # mailbox pathname
  14. # name = mh.getcontext()  # name of current folder
  15. #
  16. # list = mh.listfolders() # names of top-level folders
  17. # list = mh.listallfolders() # names of all folders, including subfolders
  18. # list = mh.listsubfolders(name) # direct subfolders of given folder
  19. # list = mh.listallsubfolders(name) # all subfolders of given folder
  20. #
  21. # mh.makefolder(name)     # create new folder
  22. # mh.deletefolder(name)   # delete folder -- must have no subfolders
  23. #
  24. # f = mh.openfolder(name) # new open folder object
  25. #
  26. # f.error(format, ...)    # same as mh.error(format, ...)
  27. # path = f.getfullname()  # folder's full pathname
  28. # path = f.getsequencesfilename() # full pathname of folder's sequences file
  29. # path = f.getmessagefilename(n)  # full pathname of message n in folder
  30. #
  31. # list = f.listmessages() # list of messages in folder (as numbers)
  32. # n = f.getcurrent()      # get current message
  33. # f.setcurrent(n)         # set current message
  34. #
  35. # dict = f.getsequences() # dictionary of sequences in folder {name: list}
  36. # f.putsequences(dict)    # write sequences back to folder
  37. #
  38. # m = f.openmessage(n)    # new open message object (costs a file descriptor)
  39. # m is a derived class of mimetools.Message(rfc822.Message), with:
  40. # s = m.getheadertext()   # text of message's headers
  41. # s = m.getheadertext(pred) # text of message's headers, filtered by pred
  42. # s = m.getbodytext()     # text of message's body, decoded
  43. # s = m.getbodytext(0)    # text of message's body, not decoded
  44. #
  45. # XXX To do, functionality:
  46. # - remove, refile messages
  47. # - annotate messages
  48. # - create, send messages
  49. #
  50. # XXX To do, orgaanization:
  51. # - move IntSet to separate file
  52. # - move most Message functionality to module mimetools
  53.  
  54.  
  55. # Customizable defaults
  56.  
  57. MH_PROFILE = '~/.mh_profile'
  58. PATH = '~/Mail'
  59. MH_SEQUENCES = '.mh_sequences'
  60. FOLDER_PROTECT = 0700
  61.  
  62.  
  63. # Imported modules
  64.  
  65. import os
  66. from stat import ST_NLINK
  67. import regex
  68. import string
  69. import mimetools
  70. import multifile
  71.  
  72.  
  73. # Exported constants
  74.  
  75. Error = 'mhlib.Error'
  76.  
  77.  
  78. # Class representing a particular collection of folders.
  79. # Optional constructor arguments are the pathname for the directory
  80. # containing the collection, and the MH profile to use.
  81. # If either is omitted or empty a default is used; the default
  82. # directory is taken from the MH profile if it is specified there.
  83.  
  84. class MH:
  85.  
  86.     # Constructor
  87.     def __init__(self, path = None, profile = None):
  88.         if not profile: profile = MH_PROFILE
  89.         self.profile = os.path.expanduser(profile)
  90.         if not path: path = self.getprofile('Path')
  91.         if not path: path = PATH
  92.         if not os.path.isabs(path) and path[0] != '~':
  93.             path = os.path.join('~', path)
  94.         path = os.path.expanduser(path)
  95.         if not os.path.isdir(path): raise Error, 'MH() path not found'
  96.         self.path = path
  97.  
  98.     # String representation
  99.     def __repr__(self):
  100.         return 'MH(%s, %s)' % (`self.path`, `self.profile`)
  101.  
  102.     # Routine to print an error.  May be overridden by a derived class
  103.     def error(self, msg, *args):
  104.         sys.stderr.write('MH error: %\n' % (msg % args))
  105.  
  106.     # Return a profile entry, None if not found
  107.     def getprofile(self, key):
  108.         return pickline(self.profile, key)
  109.  
  110.     # Return the path (the name of the collection's directory)
  111.     def getpath(self):
  112.         return self.path
  113.  
  114.     # Return the name of the current folder
  115.     def getcontext(self):
  116.         context = pickline(os.path.join(self.getpath(), 'context'),
  117.               'Current-Folder')
  118.         if not context: context = 'inbox'
  119.         return context
  120.  
  121.     # Return the names of the top-level folders
  122.     def listfolders(self):
  123.         folders = []
  124.         path = self.getpath()
  125.         for name in os.listdir(path):
  126.             if name in (os.curdir, os.pardir): continue
  127.             fullname = os.path.join(path, name)
  128.             if os.path.isdir(fullname):
  129.                 folders.append(name)
  130.         folders.sort()
  131.         return folders
  132.  
  133.     # Return the names of the subfolders in a given folder
  134.     # (prefixed with the given folder name)
  135.     def listsubfolders(self, name):
  136.         fullname = os.path.join(self.path, name)
  137.         # Get the link count so we can avoid listing folders
  138.         # that have no subfolders.
  139.         st = os.stat(fullname)
  140.         nlinks = st[ST_NLINK]
  141.         if nlinks <= 2:
  142.             return []
  143.         subfolders = []
  144.         subnames = os.listdir(fullname)
  145.         for subname in subnames:
  146.             if subname in (os.curdir, os.pardir): continue
  147.             fullsubname = os.path.join(fullname, subname)
  148.             if os.path.isdir(fullsubname):
  149.                 name_subname = os.path.join(name, subname)
  150.                 subfolders.append(name_subname)
  151.                 # Stop looking for subfolders when
  152.                 # we've seen them all
  153.                 nlinks = nlinks - 1
  154.                 if nlinks <= 2:
  155.                     break
  156.         subfolders.sort()
  157.         return subfolders
  158.  
  159.     # Return the names of all folders, including subfolders, recursively
  160.     def listallfolders(self):
  161.         return self.listallsubfolders('')
  162.  
  163.     # Return the names of subfolders in a given folder, recursively
  164.     def listallsubfolders(self, name):
  165.         fullname = os.path.join(self.path, name)
  166.         # Get the link count so we can avoid listing folders
  167.         # that have no subfolders.
  168.         st = os.stat(fullname)
  169.         nlinks = st[ST_NLINK]
  170.         if nlinks <= 2:
  171.             return []
  172.         subfolders = []
  173.         subnames = os.listdir(fullname)
  174.         for subname in subnames:
  175.             if subname in (os.curdir, os.pardir): continue
  176.             if subname[0] == ',' or isnumeric(subname): continue
  177.             fullsubname = os.path.join(fullname, subname)
  178.             if os.path.isdir(fullsubname):
  179.                 name_subname = os.path.join(name, subname)
  180.                 subfolders.append(name_subname)
  181.                 if not os.path.islink(fullsubname):
  182.                     subsubfolders = self.listallsubfolders(
  183.                           name_subname)
  184.                     subfolders = subfolders + subsubfolders
  185.                 # Stop looking for subfolders when
  186.                 # we've seen them all
  187.                 nlinks = nlinks - 1
  188.                 if nlinks <= 2:
  189.                     break
  190.         subfolders.sort()
  191.         return subfolders
  192.  
  193.     # Return a new Folder object for the named folder
  194.     def openfolder(self, name):
  195.         return Folder(self, name)
  196.  
  197.     # Create a new folder.  This raises os.error if the folder
  198.     # cannot be created
  199.     def makefolder(self, name):
  200.         protect = pickline(self.profile, 'Folder-Protect')
  201.         if protect and isnumeric(protect):
  202.             mode = eval('0' + protect)
  203.         else:
  204.             mode = FOLDER_PROTECT
  205.         os.mkdir(os.path.join(self.getpath(), name), mode)
  206.  
  207.     # Delete a folder.  This removes files in the folder but not
  208.     # subdirectories.  If deleting the folder itself fails it
  209.     # raises os.error
  210.     def deletefolder(self, name):
  211.         fullname = os.path.join(self.getpath(), name)
  212.         for subname in os.listdir(fullname):
  213.             if subname in (os.curdir, os.pardir): continue
  214.             fullsubname = os.path.join(fullname, subname)
  215.             try:
  216.                 os.unlink(fullsubname)
  217.             except os.error:
  218.                 self.error('%s not deleted, continuing...' %
  219.                       fullsubname)
  220.         os.rmdir(fullname)
  221.  
  222.  
  223. # Class representing a particular folder
  224.  
  225. numericprog = regex.compile('[1-9][0-9]*')
  226. def isnumeric(str):
  227.     return numericprog.match(str) == len(str)
  228.  
  229. class Folder:
  230.  
  231.     # Constructor
  232.     def __init__(self, mh, name):
  233.         self.mh = mh
  234.         self.name = name
  235.         if not os.path.isdir(self.getfullname()):
  236.             raise Error, 'no folder %s' % name
  237.  
  238.     # String representation
  239.     def __repr__(self):
  240.         return 'Folder(%s, %s)' % (`self.mh`, `self.name`)
  241.  
  242.     # Error message handler
  243.     def error(self, *args):
  244.         apply(self.mh.error, args)
  245.  
  246.     # Return the full pathname of the folder
  247.     def getfullname(self):
  248.         return os.path.join(self.mh.path, self.name)
  249.  
  250.     # Return the full pathname of the folder's sequences file
  251.     def getsequencesfilename(self):
  252.         return os.path.join(self.getfullname(), MH_SEQUENCES)
  253.  
  254.     # Return the full pathname of a message in the folder
  255.     def getmessagefilename(self, n):
  256.         return os.path.join(self.getfullname(), str(n))
  257.  
  258.     # Return list of direct subfolders
  259.     def listsubfolders(self):
  260.         return self.mh.listsubfolders(self.name)
  261.  
  262.     # Return list of all subfolders
  263.     def listallsubfolders(self):
  264.         return self.mh.listallsubfolders(self.name)
  265.  
  266.     # Return the list of messages currently present in the folder.
  267.     # As a side effect, set self.last to the last message (or 0)
  268.     def listmessages(self):
  269.         messages = []
  270.         for name in os.listdir(self.getfullname()):
  271.             if isnumeric(name):
  272.                 messages.append(eval(name))
  273.         messages.sort()
  274.         if messages:
  275.             self.last = max(messages)
  276.         else:
  277.             self.last = 0
  278.         return messages
  279.  
  280.     # Return the set of sequences for the folder
  281.     def getsequences(self):
  282.         sequences = {}
  283.         fullname = self.getsequencesfilename()
  284.         try:
  285.             f = open(fullname, 'r')
  286.         except IOError:
  287.             return sequences
  288.         while 1:
  289.             line = f.readline()
  290.             if not line: break
  291.             fields = string.splitfields(line, ':')
  292.             if len(fields) <> 2:
  293.                 self.error('bad sequence in %s: %s' %
  294.                       (fullname, string.strip(line)))
  295.             key = string.strip(fields[0])
  296.             value = IntSet(string.strip(fields[1]), ' ').tolist()
  297.             sequences[key] = value
  298.         return sequences
  299.  
  300.     # Write the set of sequences back to the folder
  301.     def putsequences(self, sequences):
  302.         fullname = self.getsequencesfilename()
  303.         f = None
  304.         for key in sequences.keys():
  305.             s = IntSet('', ' ')
  306.             s.fromlist(sequences[key])
  307.             if not f: f = open(fullname, 'w')
  308.             f.write('%s: %s\n' % (key, s.tostring()))
  309.         if not f:
  310.             try:
  311.                 os.unlink(fullname)
  312.             except os.error:
  313.                 pass
  314.         else:
  315.             f.close()
  316.  
  317.     # Return the current message.  Raise KeyError when there is none
  318.     def getcurrent(self):
  319.         return min(self.getsequences()['cur'])
  320.  
  321.     # Set the current message
  322.     def setcurrent(self, n):
  323.         updateline(self.getsequencesfilename(), 'cur', str(n), 0)
  324.  
  325.     # Open a message -- returns a Message object
  326.     def openmessage(self, n):
  327.         path = self.getmessagefilename(n)
  328.         return Message(self, n)
  329.  
  330.     # Remove one or more messages -- may raise os.error
  331.     def removemessages(self, list):
  332.         errors = []
  333.         deleted = []
  334.         for n in list:
  335.             path = self.getmessagefilename(n)
  336.             commapath = self.getmessagefilename(',' + str(n))
  337.             try:
  338.                 os.unlink(commapath)
  339.             except os.error:
  340.                 pass
  341.             try:
  342.                 os.rename(path, commapath)
  343.             except os.error, msg:
  344.                 errors.append(msg)
  345.             else:
  346.                 deleted.append(n)
  347.         if deleted:
  348.             self.removefromallsequences(deleted)
  349.         if errors:
  350.             if len(errors) == 1:
  351.                 raise os.error, errors[0]
  352.             else:
  353.                 raise os.error, ('multiple errors:', errors)
  354.  
  355.     # Refile one or more messages -- may raise os.error.
  356.     # 'tofolder' is an open folder object
  357.     def refilemessages(self, list, tofolder):
  358.         errors = []
  359.         refiled = []
  360.         for n in list:
  361.             ton = tofolder.getlast() + 1
  362.             path = self.getmessagefilename(n)
  363.             topath = tofolder.getmessagefilename(ton)
  364.             try:
  365.                 os.rename(path, topath)
  366.                 # XXX What if it's on a different filesystem?
  367.             except os.error, msg:
  368.                 errors.append(msg)
  369.             else:
  370.                 tofolder.setlast(ton)
  371.                 refiled.append(n)
  372.         if refiled:
  373.             self.removefromallsequences(refiled)
  374.         if errors:
  375.             if len(errors) == 1:
  376.                 raise os.error, errors[0]
  377.             else:
  378.                 raise os.error, ('multiple errors:', errors)
  379.  
  380.     # Remove one or more messages from all sequeuces (including last)
  381.     def removefromallsequences(self, list):
  382.         if hasattr(self, 'last') and self.last in list:
  383.             del self.last
  384.         sequences = self.getsequences()
  385.         changed = 0
  386.         for name, seq in sequences.items():
  387.             for n in list:
  388.                 if n in seq:
  389.                     seq.remove(n)
  390.                     changed = 1
  391.                     if not seq:
  392.                         del sequences[name]
  393.         if changed:
  394.             self.putsequences(sequences)
  395.  
  396.     # Return the last message number
  397.     def getlast(self):
  398.         if not hasattr(self, 'last'):
  399.             messages = self.listmessages()
  400.         return self.last
  401.  
  402.     # Set the last message number
  403.     def setlast(self, last):
  404.         if last is None:
  405.             if hasattr(self, 'last'):
  406.                 del self.last
  407.         else:
  408.             self.last = last
  409.  
  410. class Message(mimetools.Message):
  411.  
  412.     # Constructor
  413.     def __init__(self, f, n, fp = None):
  414.         self.folder = f
  415.         self.number = n
  416.         if not fp:
  417.             path = f.getmessagefilename(n)
  418.             fp = open(path, 'r')
  419.         mimetools.Message.__init__(self, fp)
  420.  
  421.     # String representation
  422.     def __repr__(self):
  423.         return 'Message(%s, %s)' % (repr(self.folder), self.number)
  424.  
  425.     # Return the message's header text as a string.  If an
  426.     # argument is specified, it is used as a filter predicate to
  427.     # decide which headers to return (its argument is the header
  428.     # name converted to lower case).
  429.     def getheadertext(self, pred = None):
  430.         if not pred:
  431.             return string.joinfields(self.headers, '')
  432.         headers = []
  433.         hit = 0
  434.         for line in self.headers:
  435.             if line[0] not in string.whitespace:
  436.                 i = string.find(line, ':')
  437.                 if i > 0:
  438.                     hit = pred(string.lower(line[:i]))
  439.             if hit: headers.append(line)
  440.         return string.joinfields(headers, '')
  441.  
  442.     # Return the message's body text as string.  This undoes a
  443.     # Content-Transfer-Encoding, but does not interpret other MIME
  444.     # features (e.g. multipart messages).  To suppress to
  445.     # decoding, pass a 0 as argument
  446.     def getbodytext(self, decode = 1):
  447.         self.fp.seek(self.startofbody)
  448.         encoding = self.getencoding()
  449.         if not decode or encoding in ('7bit', '8bit', 'binary'):
  450.             return self.fp.read()
  451.         from StringIO import StringIO
  452.         output = StringIO()
  453.         mimetools.decode(self.fp, output, encoding)
  454.         return output.getvalue()
  455.  
  456.     # Only for multipart messages: return the message's body as a
  457.     # list of SubMessage objects.  Each submessage object behaves
  458.     # (almost) as a Message object.
  459.     def getbodyparts(self):
  460.         if self.getmaintype() != 'multipart':
  461.             raise Error, \
  462.                   'Content-Type is not multipart/*'
  463.         bdry = self.getparam('boundary')
  464.         if not bdry:
  465.             raise Error, 'multipart/* without boundary param'
  466.         self.fp.seek(self.startofbody)
  467.         mf = multifile.MultiFile(self.fp)
  468.         mf.push(bdry)
  469.         parts = []
  470.         while mf.next():
  471.             n = str(self.number) + '.' + `1 + len(parts)`
  472.             part = SubMessage(self.folder, n, mf)
  473.             parts.append(part)
  474.         mf.pop()
  475.         return parts
  476.  
  477.     # Return body, either a string or a list of messages
  478.     def getbody(self):
  479.         if self.getmaintype() == 'multipart':
  480.             return self.getbodyparts()
  481.         else:
  482.             return self.getbodytext()
  483.  
  484.  
  485. class SubMessage(Message):
  486.  
  487.     # Constructor
  488.     def __init__(self, f, n, fp):
  489.         Message.__init__(self, f, n, fp)
  490.         if self.getmaintype() == 'multipart':
  491.             self.body = Message.getbodyparts(self)
  492.         else:
  493.             self.body = Message.getbodytext(self)
  494.             # XXX If this is big, should remember file pointers
  495.  
  496.     # String representation
  497.     def __repr__(self):
  498.         f, n, fp = self.folder, self.number, self.fp
  499.         return 'SubMessage(%s, %s, %s)' % (f, n, fp)
  500.  
  501.     def getbodytext(self):
  502.         if type(self.body) == type(''):
  503.             return self.body
  504.  
  505.     def getbodyparts(self):
  506.         if type(self.body) == type([]):
  507.             return self.body
  508.  
  509.     def getbody(self):
  510.         return self.body
  511.  
  512.  
  513. # Class implementing sets of integers.
  514. #
  515. # This is an efficient representation for sets consisting of several
  516. # continuous ranges, e.g. 1-100,200-400,402-1000 is represented
  517. # internally as a list of three pairs: [(1,100), (200,400),
  518. # (402,1000)].  The internal representation is always kept normalized.
  519. #
  520. # The constructor has up to three arguments:
  521. # - the string used to initialize the set (default ''),
  522. # - the separator between ranges (default ',')
  523. # - the separator between begin and end of a range (default '-')
  524. # The separators may be regular expressions and should be different.
  525. #
  526. # The tostring() function yields a string that can be passed to another
  527. # IntSet constructor; __repr__() is a valid IntSet constructor itself.
  528. #
  529. # XXX The default begin/end separator means that negative numbers are
  530. #     not supported very well.
  531. #
  532. # XXX There are currently no operations to remove set elements.
  533.  
  534. class IntSet:
  535.  
  536.     def __init__(self, data = None, sep = ',', rng = '-'):
  537.         self.pairs = []
  538.         self.sep = sep
  539.         self.rng = rng
  540.         if data: self.fromstring(data)
  541.  
  542.     def reset(self):
  543.         self.pairs = []
  544.  
  545.     def __cmp__(self, other):
  546.         return cmp(self.pairs, other.pairs)
  547.  
  548.     def __hash__(self):
  549.         return hash(self.pairs)
  550.  
  551.     def __repr__(self):
  552.         return 'IntSet(%s, %s, %s)' % (`self.tostring()`,
  553.               `self.sep`, `self.rng`)
  554.  
  555.     def normalize(self):
  556.         self.pairs.sort()
  557.         i = 1
  558.         while i < len(self.pairs):
  559.             alo, ahi = self.pairs[i-1]
  560.             blo, bhi = self.pairs[i]
  561.             if ahi >= blo-1:
  562.                 self.pairs[i-1:i+1] = [
  563.                       (alo, max(ahi, bhi))]
  564.             else:
  565.                 i = i+1
  566.  
  567.     def tostring(self):
  568.         s = ''
  569.         for lo, hi in self.pairs:
  570.             if lo == hi: t = `lo`
  571.             else: t = `lo` + self.rng + `hi`
  572.             if s: s = s + (self.sep + t)
  573.             else: s = t
  574.         return s
  575.  
  576.     def tolist(self):
  577.         l = []
  578.         for lo, hi in self.pairs:
  579.             m = range(lo, hi+1)
  580.             l = l + m
  581.         return l
  582.  
  583.     def fromlist(self, list):
  584.         for i in list:
  585.             self.append(i)
  586.  
  587.     def clone(self):
  588.         new = IntSet()
  589.         new.pairs = self.pairs[:]
  590.         return new
  591.  
  592.     def min(self):
  593.         return self.pairs[0][0]
  594.  
  595.     def max(self):
  596.         return self.pairs[-1][-1]
  597.  
  598.     def contains(self, x):
  599.         for lo, hi in self.pairs:
  600.             if lo <= x <= hi: return 1
  601.         return 0
  602.  
  603.     def append(self, x):
  604.         for i in range(len(self.pairs)):
  605.             lo, hi = self.pairs[i]
  606.             if x < lo: # Need to insert before
  607.                 if x+1 == lo:
  608.                     self.pairs[i] = (x, hi)
  609.                 else:
  610.                     self.pairs.insert(i, (x, x))
  611.                 if i > 0 and x-1 == self.pairs[i-1][1]:
  612.                     # Merge with previous
  613.                     self.pairs[i-1:i+1] = [
  614.                             (self.pairs[i-1][0],
  615.                              self.pairs[i][1])
  616.                           ]
  617.                 return
  618.             if x <= hi: # Already in set
  619.                 return
  620.         i = len(self.pairs) - 1
  621.         if i >= 0:
  622.             lo, hi = self.pairs[i]
  623.             if x-1 == hi:
  624.                 self.pairs[i] = lo, x
  625.                 return
  626.         self.pairs.append((x, x))
  627.  
  628.     def addpair(self, xlo, xhi):
  629.         if xlo > xhi: return
  630.         self.pairs.append((xlo, xhi))
  631.         self.normalize()
  632.  
  633.     def fromstring(self, data):
  634.         import string, regsub
  635.         new = []
  636.         for part in regsub.split(data, self.sep):
  637.             list = []
  638.             for subp in regsub.split(part, self.rng):
  639.                 s = string.strip(subp)
  640.                 list.append(string.atoi(s))
  641.             if len(list) == 1:
  642.                 new.append((list[0], list[0]))
  643.             elif len(list) == 2 and list[0] <= list[1]:
  644.                 new.append((list[0], list[1]))
  645.             else:
  646.                 raise ValueError, 'bad data passed to IntSet'
  647.         self.pairs = self.pairs + new
  648.         self.normalize()
  649.  
  650.  
  651. # Subroutines to read/write entries in .mh_profile and .mh_sequences
  652.  
  653. def pickline(file, key, casefold = 1):
  654.     try:
  655.         f = open(file, 'r')
  656.     except IOError:
  657.         return None
  658.     pat = key + ':'
  659.     if casefold:
  660.         prog = regex.compile(pat, regex.casefold)
  661.     else:
  662.         prog = regex.compile(pat)
  663.     while 1:
  664.         line = f.readline()
  665.         if not line: break
  666.         if prog.match(line) == len(line):
  667.             text = line[len(key)+1:]
  668.             while 1:
  669.                 line = f.readline()
  670.                 if not line or \
  671.                    line[0] not in string.whitespace:
  672.                     break
  673.                 text = text + line
  674.             return string.strip(text)
  675.     return None
  676.  
  677. def updateline(file, key, value, casefold = 1):
  678.     try:
  679.         f = open(file, 'r')
  680.         lines = f.readlines()
  681.         f.close()
  682.     except IOError:
  683.         lines = []
  684.     pat = key + ':\(.*\)\n'
  685.     if casefold:
  686.         prog = regex.compile(pat, regex.casefold)
  687.     else:
  688.         prog = regex.compile(pat)
  689.     if value is None:
  690.         newline = None
  691.     else:
  692.         newline = '%s: %s' % (key, value)
  693.     for i in range(len(lines)):
  694.         line = lines[i]
  695.         if prog.match(line) == len(line):
  696.             if newline is None:
  697.                 del lines[i]
  698.             else:
  699.                 lines[i] = newline
  700.             break
  701.     else:
  702.         if newline is not None:
  703.             lines.append(newline)
  704.     f = open(tempfile, 'w')
  705.     for line in lines:
  706.         f.write(line)
  707.     f.close()
  708.  
  709.  
  710. # Test program
  711.  
  712. def test():
  713.     global mh, f
  714.     os.system('rm -rf $HOME/Mail/@test')
  715.     mh = MH()
  716.     def do(s): print s; print eval(s)
  717.     do('mh.listfolders()')
  718.     do('mh.listallfolders()')
  719.     testfolders = ['@test', '@test/test1', '@test/test2',
  720.                '@test/test1/test11', '@test/test1/test12',
  721.                '@test/test1/test11/test111']
  722.     for t in testfolders: do('mh.makefolder(%s)' % `t`)
  723.     do('mh.listsubfolders(\'@test\')')
  724.     do('mh.listallsubfolders(\'@test\')')
  725.     f = mh.openfolder('@test')
  726.     do('f.listsubfolders()')
  727.     do('f.listallsubfolders()')
  728.     do('f.getsequences()')
  729.     seqs = f.getsequences()
  730.     seqs['foo'] = IntSet('1-10 12-20', ' ').tolist()
  731.     print seqs
  732.     f.putsequences(seqs)
  733.     do('f.getsequences()')
  734.     testfolders.reverse()
  735.     for t in testfolders: do('mh.deletefolder(%s)' % `t`)
  736.     do('mh.getcontext()')
  737.     context = mh.getcontext()
  738.     f = mh.openfolder(context)
  739.     do('f.listmessages()')
  740.     do('f.getcurrent()')
  741.  
  742.  
  743. if __name__ == '__main__':
  744.     test()
  745.