Commits

Łukasz Langa committed 5e3df8b

moving tests to a dedicated module

Comments (0)

Files changed (2)

 
 
 class Formatter:
+    """Default formatter for any object. Defines the protocol for other
+       implementations and provides a sane default (using repr)."""
+
     @classmethod
     def __accepts__(cls, other):
+        """Returns True if the specified 'other' object can be formatted
+           by this class."""
         return (hasattr(other, '__repr__') and
                 isinstance(other.__repr__, Callable))
 
     @classmethod
     def _pprint(cls, instance, width, height, buffer):
+        """Concrete formatting algorithm. By default only uses repr."""
         buffer.write(repr(instance))
 
     @classmethod
 
 
 class StringFormatter(Formatter):
+    """Formatter for strings, possibly splitting into multiple lines using
+    the ("st" "ri" "ng") interpreter concatenation representation."""
+
     @classmethod
     def __accepts__(cls, other):
         return other.__class__ is str
             result = result[len(prefix):-len(suffix)]
             scaffolding_width = len(prefix) + len(suffix) + 2 # brackets 
             real_width = max(width - scaffolding_width, 1)
-            # FIXME: naive chunking
+            # FIXME: naive chunking, doesn't handle \u1234 and other
+            #        multiletter tokens which should not be split
             chunks = round(len(result)/real_width+0.5)
             for i in range(chunks):
                 if i == 0:
             buffer.write(result)
 
 
-class ListFormatter(Formatter):
+class TupleFormatter(Formatter):
     # Whether multiple values should be printed on one line
     MULTIPLE_VALUES = True
+    # Whether to write the comma after last value
+    LAST_SEPARATOR = 'if 1 element' # possible values: 'never',
+                                    #                  'if 1 element',
+                                    #                  'always'
+    PREFIX = '('
+    SEPARATOR = ','
+    SUFFIX = ')'
 
     @classmethod
     def __accepts__(cls, other):
-        return other.__class__ is list
+        return other.__class__ is tuple
 
     @classmethod
     def _pprint(cls, instance, width, height, buffer):
                       elem_height > 1)
             return cls.MULTIPLE_VALUES and result
 
-        buffer.write("[")
+        buffer.write(cls.PREFIX)
         available_width = sys.maxsize
         first_elem = True
         for elem in instance:
             got_newline = False
             if not first_elem:
                 if enough_space():
-                    buffer.write(", ")
+                    buffer.write("{} ".format(cls.SEPARATOR))
                 else:
                     got_newline = True
-                    buffer.write(",\n")
+                    buffer.write("{}\n".format(cls.SEPARATOR))
 
             fmt_elem = select_formatter(elem)
             printed = fmt_elem.__pprint__(elem)
                 if not first_elem and not got_newline:
                     got_newline = True
                     buffer.write("\n")
-                available_width = width - buffer.width - 1
+                closing_width = max(len(cls.SEPARATOR), len(cls.SUFFIX))
+                available_width = width - buffer.width - closing_width
                 printed = fmt_elem.__pprint__(elem, width=available_width)
                 elem_repr, elem_width, elem_height = printed
 
-            r = _indent(elem_repr, 1, omit_first_line=not got_newline)
+            indented_repr = _indent(elem_repr, len(cls.PREFIX),
+                                    omit_first_line=not got_newline)
 
-            buffer.write(r)
+            buffer.write(indented_repr)
             first_elem = False
-        buffer.write("]")
+        if cls.LAST_SEPARATOR and cls.LAST_SEPARATOR != 'never':
+            if (cls.LAST_SEPARATOR == 'if 1 element' and len(instance) == 1 or
+                cls.LAST_SEPARATOR == 'always'):
+                if enough_space(len("{}{}".format(cls.SEPARATOR, cls.SUFFIX))):
+                    buffer.write(cls.SEPARATOR)
+                elif enough_space(cls.SEPARATOR):
+                    buffer.write(cls.SEPARATOR)
+                    buffer.write("\n")
+        buffer.write(cls.SUFFIX)
+
+
+class ListFormatter(TupleFormatter):
+    MULTIPLE_VALUES = True
+    LAST_SEPARATOR = 'never'
+    PREFIX = '['
+    SUFFIX = ']'
+
+    @classmethod
+    def __accepts__(cls, other):
+        return other.__class__ is list
+
+
+class DictFormatter(Formatter):
+    pass
+
 
 formatters = []
 
 _builtin_formatters = {
-    dict: Formatter,
+    dict: DictFormatter,
     list: ListFormatter,
     str: StringFormatter,
+    tuple: TupleFormatter,
 }
 
 def pprint(object, stream=sys.stdout, indent=None, width=80, depth=None):
     match is not found, matches are searched for ancestor classes in MRO order.
     """
 
-    mro = object.__mro__ if hasattr(object, '__mro__') else [object.__class__]
+    try:
+        mro = object.mro()
+    except AttributeError:
+        try:
+            mro = object.__class__.mro()
+        except AttributeError:
+            try:
+                mro = object.__class__.__mro__
+            except AttributeError:
+                mro = [object.__class__]
     for cls in mro:
         for formatter in formatters:
             if formatter == cls or (hasattr(formatter, '__accepts__') and
     both(funkylist, only_natty=True)
     both([12345] * 20, only_natty=True)
 
-if __name__ != '__main__':
+if False:
     from textwrap import dedent
     test1 = dedent("""
                    Line 1

test_nattyprint.py

+#!/usr/bin/env python3
+import collections
+import functools 
+import nattyprint
+import unittest
+
+class TestInternals(unittest.TestCase):
+    def setUp(self):
+        self._prev_formatters = nattyprint.formatters
+        nattyprint.formatters = []
+
+    def tearDown(self):
+        nattyprint.formatters = self._prev_formatters
+
+    def test_select_formatter(self):
+        def check(object, expected):
+            self.assertEqual(nattyprint.select_formatter(object),
+                             expected)
+        # string
+        check("", nattyprint.StringFormatter)
+        # bytes
+        # FIXME
+        # XXX: bytearray?
+        # list
+        check([], nattyprint.ListFormatter)
+        # tuple
+        check((None,), nattyprint.TupleFormatter)
+        # XXX: range?
+        # dictionaries
+        check({}, nattyprint.DictFormatter)
+        check(dict, nattyprint.DictFormatter)
+        check(collections.OrderedDict(), nattyprint.DictFormatter)
+        check(collections.OrderedDict, nattyprint.DictFormatter)
+        check(collections.defaultdict(lambda: None), nattyprint.DictFormatter)
+        check(collections.defaultdict, nattyprint.DictFormatter)
+        # sets
+        # FIXME
+        # other
+        check(3.14, nattyprint.Formatter)
+        check(0, nattyprint.Formatter)
+        check(False, nattyprint.Formatter)
+        check(None, nattyprint.Formatter)
+
+    def test_sizemeter(self):
+        def check(object, new_text, width, height):
+            object.write(new_text)
+            self.assertTrue(object.getvalue().endswith(new_text))
+            self.assertEqual(object.width, width)
+            self.assertEqual(object.height, height)
+        s = nattyprint._SizeMeter()
+        check(s, "123", 3, 1)
+        check(s, "456", 6, 1)
+        check(s, "", 6, 1)
+        check(s, "\n", 0, 2)
+        check(s, "qwerty", 6, 2)
+        check(s, "\nabcdef", 6, 3)
+
+class TestFormatter(unittest.TestCase):
+    def check(self, formatter, object, width=None, expected=None):
+        """Expected is a three-tuple of (text, width, height)."""
+        self.assertEqual(formatter.__pprint__(object, width=width), expected)
+
+    def test_simple_values(self):
+        f = nattyprint.Formatter()
+        check = functools.partial(self.check, f)
+        check(None, expected=("None", 4, 1))
+        check("", expected=("''", 2, 1))
+        check("qwerty", expected=("'qwerty'", 8, 1))
+        check(1020304050607080, expected=("1020304050607080", 16, 1))
+        check(1234.5678, expected=("1234.5678", 9, 1))
+
+class TestTupleFormatter(TestFormatter):
+    def test_simple_tuples(self):
+        f = nattyprint.TupleFormatter()
+        check = functools.partial(self.check, f)
+        check((), expected=("()", 2, 1))
+        check((None, ), expected=("(None,)", 7, 1))
+        check((1,2,3,4), expected=("(1, 2, 3, 4)", 12, 1))
+        check((1,2,3,4), width=8, expected=("(1, 2,\n 3, 4)", 6, 2))
+
+    def test_simple_lists(self):
+        f = nattyprint.ListFormatter()
+        check = functools.partial(self.check, f)
+        check([], expected=("[]", 2, 1))
+        check([None], expected=("[None]", 6, 1))
+        check([1,2,3,4], width=8, expected=("[1, 2,\n 3, 4]", 6, 2))
+
+if __name__ == '__main__':
+    unittest.main()