[Python-checkins] cpython (merge default -> default): merge

brett.cannon python-checkins at python.org
Tue Sep 25 21:24:29 CEST 2012


http://hg.python.org/cpython/rev/179283850b20
changeset: 79187:179283850b20
parent: 79186:82b5c1f4d44d
parent: 79185:4b626ccace1a
user: Brett Cannon <brett at python.org>
date: Tue Sep 25 15:23:49 2012 -0400
summary:
 merge
files:
 Doc/whatsnew/3.3.rst | 2 +-
 Lib/mailbox.py | 46 ++++++++++++++++++++---
 Lib/test/test_mailbox.py | 53 ++++++++++++++++++++-------
 Misc/NEWS | 2 +
 4 files changed, 80 insertions(+), 23 deletions(-)
diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst
--- a/Doc/whatsnew/3.3.rst
+++ b/Doc/whatsnew/3.3.rst
@@ -1079,7 +1079,7 @@
 contextlib
 ----------
 
-:class:`~collections.ExitStack` now provides a solid foundation for
+:class:`~contextlib.ExitStack` now provides a solid foundation for
 programmatic manipulation of context managers and similar cleanup
 functionality. Unlike the previous ``contextlib.nested`` API (which was
 deprecated and removed), the new API is designed to work correctly
diff --git a/Lib/mailbox.py b/Lib/mailbox.py
--- a/Lib/mailbox.py
+++ b/Lib/mailbox.py
@@ -208,6 +208,9 @@
 raise ValueError("String input must be ASCII-only; "
 "use bytes or a Message instead")
 
+ # Whether each message must end in a newline
+ _append_newline = False
+
 def _dump_message(self, message, target, mangle_from_=False):
 # This assumes the target file is open in binary mode.
 """Dump message contents to target file."""
@@ -219,6 +222,9 @@
 data = buffer.read()
 data = data.replace(b'\n', linesep)
 target.write(data)
+ if self._append_newline and not data.endswith(linesep):
+ # Make sure the message ends with a newline
+ target.write(linesep)
 elif isinstance(message, (str, bytes, io.StringIO)):
 if isinstance(message, io.StringIO):
 warnings.warn("Use of StringIO input is deprecated, "
@@ -230,11 +236,15 @@
 message = message.replace(b'\nFrom ', b'\n>From ')
 message = message.replace(b'\n', linesep)
 target.write(message)
+ if self._append_newline and not message.endswith(linesep):
+ # Make sure the message ends with a newline
+ target.write(linesep)
 elif hasattr(message, 'read'):
 if hasattr(message, 'buffer'):
 warnings.warn("Use of text mode files is deprecated, "
 "use a binary mode file instead", DeprecationWarning, 3)
 message = message.buffer
+ lastline = None
 while True:
 line = message.readline()
 # Universal newline support.
@@ -248,6 +258,10 @@
 line = b'>From ' + line[5:]
 line = line.replace(b'\n', linesep)
 target.write(line)
+ lastline = line
+ if self._append_newline and lastline and not lastline.endswith(linesep):
+ # Make sure the message ends with a newline
+ target.write(linesep)
 else:
 raise TypeError('Invalid message type: %s' % type(message))
 
@@ -833,30 +847,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(linesep)
+ def _post_message_hook(self, f):
+ """Called after writing each message to file f."""
+ f.write(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(b'From '):
 if len(stops) < len(starts):
+ if last_was_empty:
+ stops.append(line_pos - len(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(linesep))
- starts.append(line_pos)
- elif not line:
- stops.append(line_pos)
+ else:
+ stops.append(line_pos)
 break
+ elif line == 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
@@ -53,7 +53,7 @@
 maxDiff = None
 
 _factory = None # Overridden by subclasses to reuse tests
- _template = 'From: foo\n\n%s'
+ _template = 'From: foo\n\n%s\n'
 
 def setUp(self):
 self._path = support.TESTFN
@@ -232,7 +232,7 @@
 key0 = self._box.add(self._template % 0)
 msg = self._box.get(key0)
 self.assertEqual(msg['from'], 'foo')
- self.assertEqual(msg.get_payload(), '0')
+ self.assertEqual(msg.get_payload(), '0\n')
 self.assertIs(self._box.get('foo'), None)
 self.assertIs(self._box.get('foo', False), False)
 self._box.close()
@@ -240,14 +240,14 @@
 key1 = self._box.add(self._template % 1)
 msg = self._box.get(key1)
 self.assertEqual(msg['from'], 'foo')
- self.assertEqual(msg.get_payload(), '1')
+ self.assertEqual(msg.get_payload(), '1\n')
 
 def test_getitem(self):
 # Retrieve message using __getitem__()
 key0 = self._box.add(self._template % 0)
 msg = self._box[key0]
 self.assertEqual(msg['from'], 'foo')
- self.assertEqual(msg.get_payload(), '0')
+ self.assertEqual(msg.get_payload(), '0\n')
 self.assertRaises(KeyError, lambda: self._box['foo'])
 self._box.discard(key0)
 self.assertRaises(KeyError, lambda: self._box[key0])
@@ -259,7 +259,7 @@
 msg0 = self._box.get_message(key0)
 self.assertIsInstance(msg0, mailbox.Message)
 self.assertEqual(msg0['from'], 'foo')
- self.assertEqual(msg0.get_payload(), '0')
+ self.assertEqual(msg0.get_payload(), '0\n')
 self._check_sample(self._box.get_message(key1))
 
 def test_get_bytes(self):
@@ -432,15 +432,15 @@
 self.assertIn(key0, self._box)
 key1 = self._box.add(self._template % 1)
 self.assertIn(key1, self._box)
- self.assertEqual(self._box.pop(key0).get_payload(), '0')
+ self.assertEqual(self._box.pop(key0).get_payload(), '0\n')
 self.assertNotIn(key0, self._box)
 self.assertIn(key1, self._box)
 key2 = self._box.add(self._template % 2)
 self.assertIn(key2, self._box)
- self.assertEqual(self._box.pop(key2).get_payload(), '2')
+ self.assertEqual(self._box.pop(key2).get_payload(), '2\n')
 self.assertNotIn(key2, self._box)
 self.assertIn(key1, self._box)
- self.assertEqual(self._box.pop(key1).get_payload(), '1')
+ self.assertEqual(self._box.pop(key1).get_payload(), '1\n')
 self.assertNotIn(key1, self._box)
 self.assertEqual(len(self._box), 0)
 
@@ -635,7 +635,7 @@
 msg_returned = self._box.get_message(key)
 self.assertEqual(msg_returned.get_subdir(), 'new')
 self.assertEqual(msg_returned.get_flags(), '')
- self.assertEqual(msg_returned.get_payload(), '1')
+ self.assertEqual(msg_returned.get_payload(), '1\n')
 msg2 = mailbox.MaildirMessage(self._template % 2)
 msg2.set_info('2,S')
 self._box[key] = msg2
@@ -643,7 +643,7 @@
 msg_returned = self._box.get_message(key)
 self.assertEqual(msg_returned.get_subdir(), 'new')
 self.assertEqual(msg_returned.get_flags(), 'S')
- self.assertEqual(msg_returned.get_payload(), '3')
+ self.assertEqual(msg_returned.get_payload(), '3\n')
 
 def test_consistent_factory(self):
 # Add a message.
@@ -996,20 +996,20 @@
 
 def test_add_from_string(self):
 # Add a string starting with 'From ' to the mailbox
- key = self._box.add('From foo at bar blah\nFrom: foo\n\n0')
+ key = self._box.add('From foo at bar blah\nFrom: foo\n\n0\n')
 self.assertEqual(self._box[key].get_from(), 'foo at bar blah')
- self.assertEqual(self._box[key].get_payload(), '0')
+ self.assertEqual(self._box[key].get_payload(), '0\n')
 
 def test_add_from_bytes(self):
 # Add a byte string starting with 'From ' to the mailbox
- key = self._box.add(b'From foo at bar blah\nFrom: foo\n\n0')
+ key = self._box.add(b'From foo at bar blah\nFrom: foo\n\n0\n')
 self.assertEqual(self._box[key].get_from(), 'foo at bar blah')
- self.assertEqual(self._box[key].get_payload(), '0')
+ self.assertEqual(self._box[key].get_payload(), '0\n')
 
 def test_add_mbox_or_mmdf_message(self):
 # Add an mboxMessage or MMDFMessage
 for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage):
- msg = class_('From foo at bar blah\nFrom: foo\n\n0')
+ msg = class_('From foo at bar blah\nFrom: foo\n\n0\n')
 key = self._box.add(msg)
 
 def test_open_close_open(self):
@@ -1116,6 +1116,29 @@
 perms = st.st_mode
 self.assertFalse((perms & 0o111)) # 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
@@ -33,6 +33,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

AltStyle によって変換されたページ (->オリジナル) /