tree.py 3.7 KB
Newer Older
1
import re
HansBug's avatar
HansBug 已提交
2 3
from queue import Queue

4
from graphviz import Digraph
HansBug's avatar
HansBug 已提交
5 6
from treelib import Tree as LibTree

7 8
from .random import random_hex_with_timestamp

HansBug's avatar
HansBug 已提交
9 10 11 12 13
_ROOT_ID = '_root'
_NODE_ID_TEMP = '_node_{id}'


def build_tree(root, represent=None, iterate=None, recurse=None) -> LibTree:
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
    """
    Overview:
        Build a treelib object by an object.

    Arguments:
        - root (:obj:`Any`): Root object.
        - represent (:obj:`Optional[Callable]`): Represent function, default is primitive `repr`.
        - iterate (:obj:`Optional[Callable]`): Iterate function, default is `lambda x: x.items()`.
        - recurse (:obj:`Optional[Callable]`): Recurse check function, default is `lambda x: hasattr(x, 'items')`.

    Returns:
        - tree (:obj:`treelib.Tree`): Built tree.

    Example:
         >>> t = build_tree(
         >>>     {'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}, 'z': [1, 2], 'v': {'1': '2'}},
         >>>     represent=lambda x: '<node>' if isinstance(x, dict) else repr(x),
         >>>     recurse=lambda x: isinstance(x, dict),
         >>> )
         >>> print(t)

         The output should be

         >>> <node>
         >>> ├── 'a' --> 1
         >>> ├── 'b' --> 2
         >>> ├── 'v' --> <node>
         >>> │   └── '1' --> '2'
         >>> ├── 'x' --> <node>
         >>> │   ├── 'c' --> 3
         >>> │   └── 'd' --> 4
         >>> └── 'z' --> [1, 2]
    """
HansBug's avatar
HansBug 已提交
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
    represent = represent or repr
    iterate = iterate or (lambda x: x.items())
    recurse = recurse or (lambda x: hasattr(x, 'items'))

    _tree = LibTree()
    _tree.create_node(represent(root), _ROOT_ID)
    _index, _queue = 0, Queue()
    _queue.put((_ROOT_ID, root))

    while not _queue.empty():
        _parent_id, _parent_tree = _queue.get()

        for key, value in iterate(_parent_tree):
            _index += 1
            _current_id = _NODE_ID_TEMP.format(id=_index)
            _tree.create_node(
                "{key} --> {value}".format(key=repr(key), value=represent(value)),
                _current_id,
                _parent_id
            )
            if recurse(value):
                _queue.put((_current_id, value))

    return _tree
71 72 73 74 75 76 77 78 79 80 81 82


_NAME_PATTERN = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$')


def _title_flatten(title):
    title = re.sub(r'[^a-zA-Z0-9_]+', '_', str(title))
    title = re.sub(r'_+', '_', title)
    title = title.strip('_').lower()
    return title


HansBug's avatar
HansBug 已提交
83 84
def build_graph(root, node_id, name=None, title=None,
                root_title=None, represent=None, iterate=None, recurse=None) -> Digraph:
85 86 87
    represent = represent or repr
    iterate = iterate or (lambda x: x.items())
    recurse = recurse or (lambda x: hasattr(x, 'items'))
HansBug's avatar
HansBug 已提交
88
    root_title = root_title or '<root>'
89 90 91 92

    title = title or 'untitled_' + random_hex_with_timestamp()
    name = name or _title_flatten(title)
    graph = Digraph(name=name, comment=title)
HansBug's avatar
HansBug 已提交
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
    graph.graph_attr.update({'label': title, 'bgcolor': '#ffffff00'})
    graph.node(node_id(root), label=root_title)

    _queue = Queue()
    _queue.put((node_id(root), root, []))

    while not _queue.empty():
        _parent_id, _parent_tree, _parent_path = _queue.get()

        for key, value in iterate(_parent_tree):
            _current_path = [*_parent_path, key]
            if recurse(value):
                _current_id = node_id(value)
                _current_label = '.'.join([root_title, *_current_path])
                _queue.put((_current_id, value, _current_path))
            else:
                _current_id = node_id(_parent_tree) + "__" + _title_flatten(str(key))
                _current_label = represent(value)

            graph.node(_current_id, label=_current_label)
            graph.edge(_parent_id, _current_id, label=key)

    return graph