''' Pprint rewrite * Needs to work with AST, pyclbr, json, xml, and existing pprint * Incorporates features of reprlib and textwrap * Would be nice to be able to print general document outlines * ? Possible to create nicely formatted YAML and/or pickletools.dis() * Easy to add custom formatters # ??? Lower initial goal or replacing pprint._format with drop in substitute # and slightly expand the API # ??? check for overlap with formatter.py ''' __all__ = 'Separator', 'Format', 'Indent', 'guard', 'pprint' from collections import namedtuple #------------- Formatting Controls ---------------------------------------- Separator = namedtuple('Separator', 'sep') Format = namedtuple('Format', 'value') Indent = 'Indent' NL = 'NL' # force a newline, even if will fit on curr row #------------- Default Handlers ------------------------------------------ def guard(predicate=None, *, hasattr=None, classname=None, isinstance=None): 'Attaches a match() function attribute to a generator or function' import builtins def match(obj): return ( (predicate is None or predicate(obj)) and (hasattr is None or builtins.hasattr(obj, hasattr)) and (isinstance is None or builtins.isinstance(obj, isinstance)) and (classname is None or getattr(type(obj), '__name__', '') == classname) ) def decorating_function(user_function): user_function.match = match return user_function return decorating_function @guard() def universal_handler(obj): yield repr(obj) @guard(isinstance=dict) def dict_handler(d): yield "{" for k in sorted(d): v = d[k] yield Indent, Format(k), ': ' # rstrip() is automatic yield Indent, Indent, Format(v), Separator(',') # separators go between elements yield '}' @guard(isinstance=list) def list_handler(s): yield '[' for elem in s: yield Format(elem), Separator(',') yield ']' @guard(isinstance=tuple, hasattr='_fields') def general_named_tuple_handler(obj): yield obj.__name__, '(' for field, value in zip(obj._fields, obj): yield Format(field), '= ', Format(value) yield ')' default_handlers = [ universal_handler, dict_handler, ] #-------------- Controller ----------------------------------------------- # runner responsibilites # test the guard # decide whether consecutive yields and fit on one line or should be newlined # NL is a forced newline # rstrip the line # handle width / textwrap issues def pprint(obj, handlers=[], indent=1, level=0): for handler in reversed(default_handlers + handlers): if handler.match(obj): break prefix = [Indent] * level for row in handler(obj): row = prefix + list(row) print(row) #-------------- Test call ----------------------------------------------- if __name__ == '__main__': from collections import namedtuple Point3D = namedtuple('Point3D', 'x y z') @guard(classname='Point3D') def point3d_handler(obj): yield '<3D: %O9.2f | %09.2f | %09.2f >' % obj obj = [ dict(item='Wand of Fireballs', location=Point3D(21,18.2,17.5)), dict(item='Ring of Invisibility', location=Point3D(12,8.75,3.14)), ] obj = dict(a=1, b=2) pprint(obj, handlers=[point3d_handler], level=2)