[Python-checkins] python/nondist/sandbox/mailbox mailbox.py, 1.4, 1.5 test_mailbox.py, 1.4, 1.5 libmailbox.tex, 1.5, 1.6

gregorykjohnson@users.sourceforge.net gregorykjohnson at users.sourceforge.net
Tue Aug 2 23:46:16 CEST 2005


Update of /cvsroot/python/python/nondist/sandbox/mailbox
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv29669

Modified Files:
	mailbox.py test_mailbox.py libmailbox.tex 
Log Message:
* Implement MH (except sequences)
* Use mailbox.Error instead of IOError for non-empty folder removal


Index: mailbox.py
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/mailbox/mailbox.py,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -d -r1.4 -r1.5
--- mailbox.py	2 Aug 2005 19:18:53 -0000	1.4
+++ mailbox.py	2 Aug 2005 21:46:13 -0000	1.5
@@ -341,12 +341,12 @@
         for entry in os.listdir(os.path.join(path, 'new')) + \
                      os.listdir(os.path.join(path, 'cur')):
             if len(entry) < 1 or entry[0] != '.':
-                raise IOError, "Folder '%s' contains message" % name
+                raise Error, "Folder '%s' contains message" % name
         for entry in os.listdir(path):
             if entry != 'new' and entry != 'cur' and entry != 'tmp' and \
                os.path.isdir(os.path.join(path, entry)):
-                raise IOError, "Folder '%s' contains subdirectory '%s'" % \
-                               (name, entry)
+                raise Error, "Folder '%s' contains subdirectory '%s'" % \
+                              (name, entry)
         for root, dirs, files in os.walk(path, topdown=False):
             for entry in files:
                 os.remove(os.path.join(root, entry))
@@ -674,6 +674,176 @@
         self._next_key = len(self._toc)
 
 
+class MH(Mailbox):
+    """An MH mailbox."""
+
+    def __init__(self, path, factory=None):
+        """Initialize an MH instance."""
+        Mailbox.__init__(self, path, factory)
+        if not os.path.exists(self._path):
+            os.mkdir(self._path, 0700)
+
+    def add(self, message):
+        """Add message and return assigned key."""
+        keys = self.keys()
+        if len(keys) == 0:
+            new_key = 1
+        else:
+            new_key = max(keys) + 1
+        fd = os.open(os.path.join(self._path, str(new_key)),
+                     os.O_CREAT | os.O_EXCL | os.O_WRONLY)
+        f = os.fdopen(fd, 'w')
+        try:
+            try:
+                dotlock_inode = _lock_file(f)
+                try:
+                    self._dump_message(message, f)
+                    # XXX: update sequences based on message
+                    f.flush()
+                finally:
+                    _unlock_file(f, dotlock_inode)
+            finally:
+                f.close()
+        except:
+            os.remove(os.path.join(self._path, str(new_key)))
+            raise
+        return new_key
+
+    def remove(self, key):
+        """Remove the keyed message; raise KeyError if it doesn't exist."""
+        try:
+            os.remove(os.path.join(self._path, str(key)))
+        except OSError, e:
+            if e.errno == errno.ENOENT:
+                raise KeyError, "No message with key '%s'" % key
+            else:
+                raise
+
+    def __setitem__(self, key, message):
+        """Replace the keyed message; raise KeyError if it doesn't exist."""
+        try:
+            f_r = file(os.path.join(self._path, str(key)), 'r+')
+        except IOError, e:
+            if e.errno == errno.ENOENT:
+                raise KeyError, "No message with key '%s'" % key
+            else:
+                raise
+        try:
+            dotlock_inode = _lock_file(f_r)
+            try:
+                f_w = file(os.path.join(self._path, str(key)), 'w')
+                try:
+                    self._dump_message(message, f_w)
+                finally:
+                    f_w.close()
+            finally:
+                _unlock_file(f_r, dotlock_inode)
+        finally:
+            f_r.close()
+
+    def get_message(self, key):
+        """Return a Message representation or raise a KeyError."""
+        try:
+            f = file(os.path.join(self._path, str(key)), 'r')
+        except IOError, e:
+            if e.errno == errno.ENOENT:
+                raise KeyError, "No message with key '%s'" % key
+            else:
+                raise
+        try:
+            msg = MHMessage(f)
+        finally:
+            f.close()
+        # XXX: set sequences on msg
+        return msg
+
+    def get_string(self, key):
+        """Return a string representation or raise a KeyError."""
+        try:
+            f = file(os.path.join(self._path, str(key)), 'r')
+        except IOError, e:
+            if e.errno == errno.ENOENT:
+                raise KeyError, "No message with key '%s'" % key
+            else:
+                raise
+        try:
+            return f.read()
+        finally:
+            f.close()
+
+    def get_file(self, key):
+        """Return a file-like representation or raise a KeyError."""
+        try:
+            f = file(os.path.join(self._path, str(key)), 'r')
+        except IOError, e:
+            if e.errno == errno.ENOENT:
+                raise KeyError, "No message with key '%s'" % key
+            else:
+                raise
+        return _ProxyFile(f)
+
+    def iterkeys(self):
+        """Return an iterator over keys."""
+        for x in sorted(int(x) for x in os.listdir(self._path) if x.isdigit()):
+            yield int(x)
+
+    def has_key(self, key):
+        """Return True if the keyed message exists, False otherwise."""
+        return os.path.exists(os.path.join(self._path, str(key)))
+
+    def __len__(self):
+        """Return a count of messages in the mailbox."""
+        return len(list(self.iterkeys()))
+
+    def flush(self):
+        """Write any pending changes to the disk."""
+        return
+
+    def close(self):
+        """Flush and close the mailbox."""
+        return
+
+    def list_folders(self):
+        """Return a list of folders in this mailbox."""
+        result = []
+        for entry in os.listdir(self._path):
+            if os.path.isdir(os.path.join(self._path, entry)):
+                result.append(entry)
+        return result
+
+    def open_folder(self, name):
+        """Return an MH instance for folder name, creating it if necessary."""
+        return MH(os.path.join(self._path, name))
+
+    def remove_folder(self, name):
+        """Delete folder name."""
+        path = os.path.join(self._path, name)
+        entries = os.listdir(path)
+        if entries == ['.mh_sequences']:
+            os.remove(os.path.join(path, '.mh_sequences'))
+        elif entries == []:
+            pass
+        else:
+            raise Error, "Folder '%s' is not empty" % self._path
+        os.rmdir(path)
+
+    def list_sequences(self, name):
+        """Return a list of the names of sequences defined in the mailbox."""
+        raise NotImplementedError, 'Method not yet implemented'
+
+    def get_sequence(self, name):
+        """Return a list of the keys in sequence 'name'."""
+        raise NotImplementedError, 'Method not yet implemented'
+
+    def set_sequence(self, name):
+        """Set the keys in sequence 'name'."""
+        raise NotImplementedError, 'Method not yet implemented'
+
+    def pack(self):
+        """Eliminate numbering gaps in message names. Invalidates keys."""
+        raise NotImplementedError, 'Method not yet implemented'
+
+
 class Message(email.Message.Message):
     """Message with mailbox-format-specific properties."""
 

Index: test_mailbox.py
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/mailbox/test_mailbox.py,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -d -r1.4 -r1.5
--- test_mailbox.py	2 Aug 2005 19:18:53 -0000	1.4
+++ test_mailbox.py	2 Aug 2005 21:46:13 -0000	1.5
@@ -670,6 +670,11 @@
     _factory = lambda self, path, factory=None: mailbox.MMDF(path, factory)
 
 
+class TestMH(TestMailbox):
+
+    _factory = lambda self, path, factory=None: mailbox.MH(path, factory)
+
+
 class TestMessage(TestBase):
 
     _factory = mailbox.Message      # Overridden by subclasses to reuse tests
@@ -1440,9 +1445,10 @@
 
 
 def test_main():
-    tests = (TestMaildir, TestMbox, TestMMDF, TestMessage, TestMaildirMessage,
-             TestMboxMessage, TestMHMessage, TestBabylMessage, TestMMDFMessage,
-             TestMessageConversion, TestProxyFile, TestPartialFile)
+    tests = (TestMaildir, TestMbox, TestMMDF, TestMH, TestMessage,
+             TestMaildirMessage, TestMboxMessage, TestMHMessage,
+             TestBabylMessage, TestMMDFMessage, TestMessageConversion,
+             TestProxyFile, TestPartialFile)
     test_support.run_unittest(*tests)
 
 

Index: libmailbox.tex
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/mailbox/libmailbox.tex,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -d -r1.5 -r1.6
--- libmailbox.tex	2 Aug 2005 19:18:54 -0000	1.5
+++ libmailbox.tex	2 Aug 2005 21:46:13 -0000	1.6
@@ -291,8 +291,8 @@
 
 \begin{methoddesc}{remove_folder}{name}
 Delete the folder whose name is \var{name}. If the folder contains any
-messages, an \exception{IOError} exception will be raised and the folder will
-not be deleted.
+messages, a \exception{mailbox.Error} exception will be raised and the folder
+will not be deleted.
 \end{methoddesc}
 
 \begin{methoddesc}{clean}{}
@@ -404,13 +404,18 @@
 \dfn{folders}) in addition to messages. Folders may be nested indefinitely.
 
 MH mailboxes support \dfn{sequences}, which are named lists used to logically
-group messages without moving them to sub-folders. Some mail reading programs
+group messages without moving them to sub-folders. Sequences are defined in a
+file called \file{.mh_sequences} in each folder. Some mail reading programs
 (although not the standard \program{mh} and \program{nmh} implementations) use
 sequences to the same end as flags are used in other formats: unread messages
 are added to the "unseen" sequence, replied-to messages are added to the
 "replied" sequence, and important messages are added upon request to the
 "flagged" sequence.
 
+\class{MH} manipulates MH mailboxes, but it does not attempt to emulate
+\program{mh}. In particular, it does not access or modify \file{context} or
+\file{.mh_profile} files.
+
 \class{MH} instances have all of the methods of \class{Mailbox} in addition to
 the following:
 
@@ -426,8 +431,8 @@
 
 \begin{methoddesc}{remove_folder}{name}
 Delete the folder whose name is \var{name}. If the folder contains any
-messages, an \exception{IOError} exception will be raised and the folder will
-not be deleted.
+messages, a \exception{mailbox.Error} exception will be raised and the folder
+will not be deleted.
 \end{methoddesc}
 
 \begin{methoddesc}{list_sequences}{}
@@ -468,14 +473,14 @@
 nothing.
 \end{methoddesc}
 
-\begin{methoddesc}{flush}{}
+\begin{methoddesc}{close}{}
 \class{MH} instances do not keep any open files, so this method does nothing.
 \end{methoddesc}
 
 \begin{classdesc}{MH}{path\optional{, factory}}
 A subclass of \class{Mailbox} for mailboxes in MH format. Parameters \var{path}
-and \var{factory} has the same meaning as with the module-level \method{open()}
-function.
+and \var{factory} have the same meaning as with the module-level
+\method{open()} function.
 \end{classdesc}
 
 \class{MH} instances have all of the methods of \class{Mailbox} in addition to
@@ -500,7 +505,7 @@
 
 \begin{classdesc}{Babyl}{path\optional{, factory}}
 A subclass of \class{Mailbox} for mailboxes in Babyl format. Parameters
-\var{path} and \var{factory} has the same meaning as with the module-level
+\var{path} and \var{factory} have the same meaning as with the module-level
 \method{open()} function.
 \end{classdesc}
 
@@ -531,7 +536,7 @@
 
 \begin{classdesc}{MMDF}{path\optional{, factory}}
 A subclass of \class{Mailbox} for mailboxes in MMDF format. Parameters
-\var{path} and \var{factory} has the same meaning as with the module-level
+\var{path} and \var{factory} have the same meaning as with the module-level
 \method{open()} function.
 \end{classdesc}
 



More information about the Python-checkins mailing list