Source code for anytree.render

# -*- coding: utf-8 -*-
"""
Tree Rendering.

* :any:`RenderTree` using the following styles:
    * :any:`AsciiStyle`
    * :any:`ContStyle`
    * :any:`ContRoundStyle`
    * :any:`DoubleStyle`
"""

import six


[docs]class AbstractStyle(object): def __init__(self, vertical, cont, end): """ Tree Render Style. Args: vertical: Sign for vertical line. cont: Chars for a continued branch. end: Chars for the last branch. """ super(AbstractStyle, self).__init__() self.vertical = vertical self.cont = cont self.end = end assert (len(cont) == len(vertical) and len(cont) == len(end)), ( "'%s', '%s' and '%s' need to have equal length" % (vertical, cont, end)) @property def empty(self): """Empty string as placeholder.""" return ' ' * len(self.end) def __repr__(self): classname = self.__class__.__name__ return "%s()" % classname
[docs]class AsciiStyle(AbstractStyle): def __init__(self): """ Ascii style. >>> from anytree import Node, RenderTree >>> root = Node("root") >>> s0 = Node("sub0", parent=root) >>> s0b = Node("sub0B", parent=s0) >>> s0a = Node("sub0A", parent=s0) >>> s1 = Node("sub1", parent=root) >>> print(RenderTree(root, style=AsciiStyle())) Node('/root') |-- Node('/root/sub0') | |-- Node('/root/sub0/sub0B') | +-- Node('/root/sub0/sub0A') +-- Node('/root/sub1') """ super(AsciiStyle, self).__init__(u'| ', u'|-- ', u'+-- ')
[docs]class ContStyle(AbstractStyle): def __init__(self): u""" Continued style, without gaps. >>> from anytree import Node, RenderTree >>> root = Node("root") >>> s0 = Node("sub0", parent=root) >>> s0b = Node("sub0B", parent=s0) >>> s0a = Node("sub0A", parent=s0) >>> s1 = Node("sub1", parent=root) >>> print(RenderTree(root, style=ContStyle())) Node('/root') ├── Node('/root/sub0') │ ├── Node('/root/sub0/sub0B') │ └── Node('/root/sub0/sub0A') └── Node('/root/sub1') """ super(ContStyle, self).__init__(u'\u2502 ', u'\u251c\u2500\u2500 ', u'\u2514\u2500\u2500 ')
[docs]class ContRoundStyle(AbstractStyle): def __init__(self): u""" Continued style, without gaps, round edges. >>> from anytree import Node, RenderTree >>> root = Node("root") >>> s0 = Node("sub0", parent=root) >>> s0b = Node("sub0B", parent=s0) >>> s0a = Node("sub0A", parent=s0) >>> s1 = Node("sub1", parent=root) >>> print(RenderTree(root, style=ContRoundStyle())) Node('/root') ├── Node('/root/sub0') │ ├── Node('/root/sub0/sub0B') │ ╰── Node('/root/sub0/sub0A') ╰── Node('/root/sub1') """ super(ContRoundStyle, self).__init__(u'\u2502 ', u'\u251c\u2500\u2500 ', u'\u2570\u2500\u2500 ')
[docs]class DoubleStyle(AbstractStyle): def __init__(self): u""" Double line style, without gaps. >>> from anytree import Node, RenderTree >>> root = Node("root") >>> s0 = Node("sub0", parent=root) >>> s0b = Node("sub0B", parent=s0) >>> s0a = Node("sub0A", parent=s0) >>> s1 = Node("sub1", parent=root) >>> print(RenderTree(root, style=DoubleStyle)) Node('/root') ╠══ Node('/root/sub0') ║ ╠══ Node('/root/sub0/sub0B') ║ ╚══ Node('/root/sub0/sub0A') ╚══ Node('/root/sub1') """ super(DoubleStyle, self).__init__(u'\u2551 ', u'\u2560\u2550\u2550 ', u'\u255a\u2550\u2550 ')
@six.python_2_unicode_compatible
[docs]class RenderTree(object): def __init__(self, node, style=ContStyle(), childiter=list): u""" Render tree starting at `node`. Keyword Args: style (AbstractStyle): Render Style. childiter: Child iterator. :any:`RenderTree` is an iterator, returning a tuple with 3 items: `pre` tree prefix. `fill` filling for multiline entries. `node` :any:`NodeMixin` object. It is up to the user to assemble these parts to a whole. >>> from anytree import Node, RenderTree >>> root = Node("root", lines=["c0fe", "c0de"]) >>> s0 = Node("sub0", parent=root, lines=["ha", "ba"]) >>> s0b = Node("sub0B", parent=s0, lines=["1", "2", "3"]) >>> s0a = Node("sub0A", parent=s0, lines=["a", "b"]) >>> s1 = Node("sub1", parent=root, lines=["Z"]) Simple one line: >>> for pre, _, node in RenderTree(root): ... print("%s%s" % (pre, node.name)) root ├── sub0 │ ├── sub0B │ └── sub0A └── sub1 Multiline: >>> for pre, fill, node in RenderTree(root): ... print("%s%s" % (pre, node.lines[0])) ... for line in node.lines[1:]: ... print("%s%s" % (fill, line)) c0fe c0de ├── ha │ ba │ ├── 1 │ │ 2 │ │ 3 │ └── a │ b └── Z The `childiter` is responsible for iterating over child nodes at the same level. An reversed order can be achived by using `reversed`. >>> for pre, _, node in RenderTree(root, childiter=reversed): ... print("%s%s" % (pre, node.name)) root ├── sub1 └── sub0 ├── sub0A └── sub0B Or writing your own sort function: >>> def mysort(items): ... return sorted(items, key=lambda item: item.name) >>> for pre, _, node in RenderTree(root, childiter=mysort): ... print("%s%s" % (pre, node.name)) root ├── sub0 │ ├── sub0A │ └── sub0B └── sub1 """ if not isinstance(style, AbstractStyle): style = style() self.node = node self.style = style self.childiter = childiter def __iter__(self): return self.__next(self.node, tuple()) def __next(self, node, continues): # item if not continues: yield u'', u'', node else: style = self.style indent = ''.join([style.vertical if cont else style.empty for cont in continues[:-1]]) branch = style.cont if continues[-1] else style.end pre = indent + branch fill = ''.join([style.vertical if cont else style.empty for cont in continues]) yield pre, fill, node # children children = node.children if children: lastidx = len(children) - 1 for idx, child in enumerate(self.childiter(children)): for grandchild in self.__next(child, continues + (idx != lastidx, )): yield grandchild def __str__(self): lines = ["%s%r" % (pre, node) for pre, _, node in self] return "\n".join(lines) def __repr__(self): classname = self.__class__.__name__ args = [repr(self.node), "style=%s" % repr(self.style), "childiter=%s" % repr(self.childiter)] return "%s(%s)" % (classname, ", ".join(args))