[Python-checkins] cpython (2.7): #15222: Insert blank line after each message in mbox mailboxes

petri.lehtinen python-checkins at python.org
Tue Sep 25 21:08:39 CEST 2012


http://hg.python.org/cpython/rev/d903d4981e33
changeset:   79182:d903d4981e33
branch:      2.7
user:        Petri Lehtinen <petri at digip.org>
date:        Tue Sep 25 21:57:59 2012 +0300
summary:
  #15222: Insert blank line after each message in mbox mailboxes

files:
  Lib/mailbox.py           |  49 +++++++++++++++++++++++----
  Lib/test/test_mailbox.py |  23 +++++++++++++
  Misc/NEWS                |   2 +
  3 files changed, 66 insertions(+), 8 deletions(-)


diff --git a/Lib/mailbox.py b/Lib/mailbox.py
--- a/Lib/mailbox.py
+++ b/Lib/mailbox.py
@@ -197,6 +197,9 @@
         """Flush and close the mailbox."""
         raise NotImplementedError('Method must be implemented by subclass')
 
+    # Whether each message must end in a newline
+    _append_newline = False
+
     def _dump_message(self, message, target, mangle_from_=False):
         # Most files are opened in binary mode to allow predictable seeking.
         # To get native line endings on disk, the user-friendly \n line endings
@@ -207,13 +210,21 @@
             gen = email.generator.Generator(buffer, mangle_from_, 0)
             gen.flatten(message)
             buffer.seek(0)
-            target.write(buffer.read().replace('\n', os.linesep))
+            data = buffer.read().replace('\n', os.linesep)
+            target.write(data)
+            if self._append_newline and not data.endswith(os.linesep):
+                # Make sure the message ends with a newline
+                target.write(os.linesep)
         elif isinstance(message, str):
             if mangle_from_:
                 message = message.replace('\nFrom ', '\n>From ')
             message = message.replace('\n', os.linesep)
             target.write(message)
+            if self._append_newline and not message.endswith(os.linesep):
+                # Make sure the message ends with a newline
+                target.write(os.linesep)
         elif hasattr(message, 'read'):
+            lastline = None
             while True:
                 line = message.readline()
                 if line == '':
@@ -222,6 +233,10 @@
                     line = '>From ' + line[5:]
                 line = line.replace('\n', os.linesep)
                 target.write(line)
+                lastline = line
+            if self._append_newline and lastline and not lastline.endswith(os.linesep):
+                # Make sure the message ends with a newline
+                target.write(os.linesep)
         else:
             raise TypeError('Invalid message type: %s' % type(message))
 
@@ -797,30 +812,48 @@
 
     _mangle_from_ = True
 
+    # All messages must end in a newline character, and
+    # _post_message_hooks outputs an empty line between messages.
+    _append_newline = True
+
     def __init__(self, path, factory=None, create=True):
         """Initialize an mbox mailbox."""
         self._message_factory = mboxMessage
         _mboxMMDF.__init__(self, path, factory, create)
 
-    def _pre_message_hook(self, f):
-        """Called before writing each message to file f."""
-        if f.tell() != 0:
-            f.write(os.linesep)
+    def _post_message_hook(self, f):
+        """Called after writing each message to file f."""
+        f.write(os.linesep)
 
     def _generate_toc(self):
         """Generate key-to-(start, stop) table of contents."""
         starts, stops = [], []
+        last_was_empty = False
         self._file.seek(0)
         while True:
             line_pos = self._file.tell()
             line = self._file.readline()
             if line.startswith('From '):
                 if len(stops) < len(starts):
+                    if last_was_empty:
+                        stops.append(line_pos - len(os.linesep))
+                    else:
+                        # The last line before the "From " line wasn't
+                        # blank, but we consider it a start of a
+                        # message anyway.
+                        stops.append(line_pos)
+                starts.append(line_pos)
+                last_was_empty = False
+            elif not line:
+                if last_was_empty:
                     stops.append(line_pos - len(os.linesep))
-                starts.append(line_pos)
-            elif line == '':
-                stops.append(line_pos)
+                else:
+                    stops.append(line_pos)
                 break
+            elif line == os.linesep:
+                last_was_empty = True
+            else:
+                last_was_empty = False
         self._toc = dict(enumerate(zip(starts, stops)))
         self._next_key = len(self._toc)
         self._file_length = self._file.tell()
diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py
--- a/Lib/test/test_mailbox.py
+++ b/Lib/test/test_mailbox.py
@@ -1003,6 +1003,29 @@
             perms = st.st_mode
             self.assertFalse((perms & 0111)) # Execute bits should all be off.
 
+    def test_terminating_newline(self):
+        message = email.message.Message()
+        message['From'] = 'john at example.com'
+        message.set_payload('No newline at the end')
+        i = self._box.add(message)
+
+        # A newline should have been appended to the payload
+        message = self._box.get(i)
+        self.assertEqual(message.get_payload(), 'No newline at the end\n')
+
+    def test_message_separator(self):
+        # Check there's always a single blank line after each message
+        self._box.add('From: foo\n\n0')  # No newline at the end
+        with open(self._path) as f:
+            data = f.read()
+            self.assertEqual(data[-3:], '0\n\n')
+
+        self._box.add('From: foo\n\n0\n')  # Newline at the end
+        with open(self._path) as f:
+            data = f.read()
+            self.assertEqual(data[-3:], '0\n\n')
+
+
 class TestMMDF(_TestMboxMMDF, unittest.TestCase):
 
     _factory = lambda self, path, factory=None: mailbox.MMDF(path, factory)
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -107,6 +107,8 @@
 Library
 -------
 
+- Issue #15222: Insert blank line after each message in mbox mailboxes
+
 - Issue #16013: Fix CSV Reader parsing issue with ending quote characters.
   Patch by Serhiy Storchaka.
 

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list