diff -r 6f23bc5d480e Lib/pprint.py --- a/Lib/pprint.py Fri Nov 28 13:28:25 2014 +0100 +++ b/Lib/pprint.py Fri Nov 28 21:27:37 2014 +0200 @@ -161,7 +161,7 @@ class PrettyPrinter: return rep = self._repr(object, context, level - 1) typ = type(object) - max_width = self._width - 1 - indent - allowance + max_width = self._width - indent - allowance sepLines = len(rep) > max_width write = stream.write @@ -174,24 +174,14 @@ class PrettyPrinter: length = len(object) if length: context[objid] = 1 - indent = indent + self._indent_per_level if issubclass(typ, _OrderedDict): items = list(object.items()) else: items = sorted(object.items(), key=_safe_tuple) - key, ent = items[0] - rep = self._repr(key, context, level) - write(rep) - write(': ') - self._format(ent, stream, indent + len(rep) + 2, - allowance + 1, context, level) - if length > 1: - for key, ent in items[1:]: - rep = self._repr(key, context, level) - write(',\n%s%s: ' % (' '*indent, rep)) - self._format(ent, stream, indent + len(rep) + 2, - allowance + 1, context, level) - indent = indent - self._indent_per_level + self._format_dict_items(items, stream, + indent + self._indent_per_level, + allowance + 1, + context, level) del context[objid] write('}') return @@ -207,7 +197,10 @@ class PrettyPrinter: endchar = ']' elif issubclass(typ, tuple): write('(') - endchar = ')' + if length == 1: + endchar = ',)' + else: + endchar = ')' else: if not length: write(rep) @@ -227,10 +220,9 @@ class PrettyPrinter: context[objid] = 1 self._format_items(object, stream, indent + self._indent_per_level, - allowance + 1, context, level) + allowance + len(endchar), + context, level) del context[objid] - if issubclass(typ, tuple) and length == 1: - write(',') write(endchar) return @@ -240,19 +232,27 @@ class PrettyPrinter: Return a list of string literals comprising the repr() of the given string using literal concatenation. """ + max_width1 = max_width = self._width - indent lines = s.splitlines(True) for i, line in enumerate(lines): rep = repr(line) - if len(rep) <= max_width: + if i == len(lines) - 1: + max_width1 -= allowance + if len(rep) <= max_width1: yield rep else: # A list of alternating (non-space, space) strings - parts = re.split(r'(\s+)', line) + [''] + parts = re.findall(r'\S*\s*', line) + assert parts + assert not parts[-1] + parts.pop() # drop empty last part + max_width2 = max_width current = '' - for i in range(0, len(parts), 2): - part = parts[i] + parts[i+1] + for j, part in enumerate(parts): candidate = current + part - if len(repr(candidate)) > max_width: + if j == len(parts) - 1 and i == len(lines) - 1: + max_width2 -= allowance + if len(repr(candidate)) > max_width2: if current: yield repr(current) current = part @@ -267,12 +267,41 @@ class PrettyPrinter: return write(rep) + def _format_dict_items(self, items, stream, indent, allowance, context, + level): + write = stream.write + delimnl = ',\n' + ' ' * indent + last_index = len(items) - 1 + for i, (key, ent) in enumerate(items): + last = i == last_index + rep = self._repr(key, context, level) + write(rep) + write(': ') + self._format(ent, stream, indent + len(rep) + 2, + allowance if last else 1, + context, level) + if not last: + write(delimnl) + def _format_items(self, items, stream, indent, allowance, context, level): write = stream.write delimnl = ',\n' + ' ' * indent delim = '' - width = max_width = self._width - indent - allowance + 2 - for ent in items: + width = max_width = self._width - indent + 1 + it = iter(items) + try: + next_ent = next(it) + except StopIteration: + return + last = False + while not last: + ent = next_ent + try: + next_ent = next(it) + except StopIteration: + last = True + max_width -= allowance + width -= allowance if self._compact: rep = self._repr(ent, context, level) w = len(rep) + 2 @@ -288,7 +317,9 @@ class PrettyPrinter: continue write(delim) delim = delimnl - self._format(ent, stream, indent, allowance, context, level) + self._format(ent, stream, indent, + allowance if last else 1, + context, level) def _repr(self, object, context, level): repr, readable, recursive = self.format(object, context.copy(), diff -r 6f23bc5d480e Lib/test/test_pprint.py --- a/Lib/test/test_pprint.py Fri Nov 28 13:28:25 2014 +0100 +++ b/Lib/test/test_pprint.py Fri Nov 28 21:27:37 2014 +0200 @@ -192,10 +192,52 @@ class QueryTestCase(unittest.TestCase): o = [o1, o2] expected = """\ [ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + {'first': 1, 'second': 2, 'third': 3}]""" + self.assertEqual(pprint.pformat(o, indent=4, width=42), expected) + expected = """\ +[ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], { 'first': 1, 'second': 2, 'third': 3}]""" - self.assertEqual(pprint.pformat(o, indent=4, width=42), expected) + self.assertEqual(pprint.pformat(o, indent=4, width=41), expected) + + def test_width(self): + expected = """\ +[[[[[[1, 2, 3], + '1 2']]]], + {1: [1, 2, 3], + 2: [12, 34]}, + 'abc def ghi', + ('ab cd ef',), + set2({1, 23}), + [[[[[1, 2, 3], + '1 2']]]]]""" + o = eval(expected) + self.assertEqual(pprint.pformat(o, width=15), expected) + self.assertEqual(pprint.pformat(o, width=16), expected) + self.assertEqual(pprint.pformat(o, width=25), expected) + self.assertEqual(pprint.pformat(o, width=14), """\ +[[[[[[1, + 2, + 3], + '1 ' + '2']]]], + {1: [1, + 2, + 3], + 2: [12, + 34]}, + 'abc def ' + 'ghi', + ('ab cd ' + 'ef',), + set2({1, + 23}), + [[[[[1, + 2, + 3], + '1 ' + '2']]]]]""") def test_sorted_dict(self): # Starting in Python 2.5, pprint sorts dict displays by key regardless @@ -537,10 +579,10 @@ frozenset2({0, fox = 'the quick brown fox jumped over a lazy dog' self.assertEqual(pprint.pformat(fox, width=20), """\ 'the quick brown ' -'fox jumped over ' -'a lazy dog'""") +'fox jumped over a ' +'lazy dog'""") self.assertEqual(pprint.pformat({'a': 1, 'b': fox, 'c': 2}, - width=26), """\ + width=25), """\ {'a': 1, 'b': 'the quick brown ' 'fox jumped over ' @@ -552,12 +594,34 @@ frozenset2({0, # - non-ASCII is allowed # - an apostrophe doesn't disrupt the pprint special = "Portons dix bons \"whiskys\"\nà l'avocat goujat\t qui fumait au zoo" + self.assertEqual(pprint.pformat(special, width=68), repr(special)) + self.assertEqual(pprint.pformat(special, width=30), """\ +'Portons dix bons "whiskys"\\n' +"à l'avocat goujat\\t qui " +'fumait au zoo'""") self.assertEqual(pprint.pformat(special, width=20), """\ 'Portons dix bons ' '"whiskys"\\n' "à l'avocat " 'goujat\\t qui ' 'fumait au zoo'""") + self.assertEqual(pprint.pformat([[[[[special]]]]], width=35), """\ +[[[[['Portons dix bons "whiskys"\\n' + "à l'avocat goujat\\t qui " + 'fumait au zoo']]]]]""") + self.assertEqual(pprint.pformat([[[[[special]]]]], width=25), """\ +[[[[['Portons dix bons ' + '"whiskys"\\n' + "à l'avocat " + 'goujat\\t qui ' + 'fumait au zoo']]]]]""") + self.assertEqual(pprint.pformat([[[[[special]]]]], width=23), """\ +[[[[['Portons dix ' + 'bons "whiskys"\\n' + "à l'avocat " + 'goujat\\t qui ' + 'fumait au ' + 'zoo']]]]]""") # An unwrappable string is formatted as its repr unwrappable = "x" * 100 self.assertEqual(pprint.pformat(unwrappable, width=80), repr(unwrappable)) @@ -578,7 +642,19 @@ frozenset2({0, 14, 15], [], [0], [0, 1], [0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 3, 4]]""" - self.assertEqual(pprint.pformat(o, width=48, compact=True), expected) + self.assertEqual(pprint.pformat(o, width=47, compact=True), expected) + + def test_compact_width(self): + levels = 20 + number = 10 + o = [0] * number + for i in range(levels - 1): + o = [o] + for w in range(levels * 2 + 1, levels + 3 * number - 1): + lines = pprint.pformat(o, width=w, compact=True).splitlines() + maxwidth = max(map(len, lines)) + self.assertLessEqual(maxwidth, w) + self.assertGreater(maxwidth, w - 3) class DottedPrettyPrinter(pprint.PrettyPrinter):