[
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) [2019] [Spandan Bemby]\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "Ascii Tree\n==========\n\nGenerate beautiful ascii trees.\n```\nI think that I shall never see\nA graph more lovely than a tree.\nA tree whose crucial property\nIs loop-free connectivity.\nA tree that must be sure to span\nSo packets can reach every LAN.\nFirst, the root must be selected.\nBy ID, it is elected.\nLeast-cost paths from root are traced.\nIn the tree, these paths are placed.\nA mesh is made by folks like me,\nThen bridges find a spanning tree.\n    -Radia Perlman\n```\nInstallation\n============\n`git clone ...`\n\n`cd ...`\n\n`python3 setup.py install`\n\nUsage\n=====\nThis library can print **arbitrary** trees. This requires\nyou to specify how the value of a node, and list\nof it's children can be extracted from the node object.\nFor example, consider the following n-ary tree node class\n```\n>>> class NTreeNode:\n...    def __init__(self, val):\n...        self.val = val\n...        self.children = []\n```\nWe can extract it's value and children like\n\n```\n>>> def get_value(node):\n...     return node.value\n...\n>>> def get_children(node):\n...     return node.children\n...\n```\nFirst, we'll construct a dummy tree.\n```\n>>> root = NTreeNode('Lorem ipsum dolor sit amet')\n>>> root.children = [NTreeNode('consectetur adipiscing elit.'), NTreeNode('Etiam laoreet congue')]\n>>> root.children[0].children = [NTreeNode('Pellentesque finibus metus eget'), NTreeNode('ante aliquet ullamcorper')]\n>>> root.children[1].children = [NTreeNode('Morbi porta, diam at imperdiet venenatis'),  NTreeNode('neque eros bibendum tortor, quis')]\n```\nNow we can print this as an ascii (the library originally only used ascii characters) tree like\n```\n>>> from ascii_tree import make_and_print_tree\n>>> make_and_print_tree(root, get_value, get_children)\npage: 0\n                            ┌───────────────────┐                                                                       \n                            │                   │                                                                       \n                            │ Lorem ipsum dolo  │                                                                       \n                            │ r sit amet        ├────────────────────────────────────┐                                  \n                            │                   │                                    │                                  \n                            └┬──────────────────┘                                    │                                  \n                             │                                                       │                                  \n                             │                                                       │                                  \n                             │                                                       │                                  \n                             │                                                       │                                  \n                             │                                                       │                                  \n                   ┌─────────┴─────────┐                                   ┌─────────┴─────────┐                        \n                   │                   │                                   │                   │                        \n                   │ consectetur adip  │                                   │ Etiam laoreet co  │                        \n                   │ iscing elit.      ├────────────────────┐              │ ngue              │                        \n                   │                   │                    │              │                   │                        \n                   └──┬────────────────┘                    │              └───────────────────┘                        \n                      │                                     │                                                           \n                      │                                     │                                                           \n                      │                                     │                                                           \n                      │                                     │                                                           \n                      │                                     │                                                           \n            ┌─────────┴─────────┐                 ┌─────────┴─────────┐                                                 \n            │                   │                 │                   │                                                 \n            │ Pellentesque fin  │                 │ ante aliquet ull  │                                                 \n          ┌─┤ ibus metus eget   ├──┐              │ amcorper          │                                                 \n          │ │                   │  │              │                   │                                                 \n          │ └───────────────────┘  │              └───────────────────┘                                                 \n          │                        │                                                                                    \n          │                        │                                                                                    \n          │                        │                                                                                    \n          │                        │                                                                                    \n          │                        │                                                                                    \n┌─────────┴─────────┐    ┌─────────┴─────────┐                                                                          \n│                   │    │                   │                                                                          \n│ Sed nec nibh cur  │    │ volutpat lacus a  │                                                                          \n│ sus               │    │ t, euismod mi     │                                                                          \n│                   │    │                   │                                                                          \n└───────────────────┘    └───────────────────┘                                                                          \n```\nWe can also print the tree using only ascii characters like:\n```\n>>> update_param('charset', 'ascii')\n>>> make_and_print_tree(root, lambda n: n.val, lambda n: n.children)\npage: 0\n                            ---------------------                                                                       \n                            |                   |                                                                       \n                            | Lorem ipsum dolo  |                                                                       \n                            | r sit amet        |-------------------------------------                                  \n                            |                   |                                    |                                  \n                            ---------------------                                    |                                  \n                             |                                                       |                                  \n                             |                                                       |                                  \n                             |                                                       |                                  \n                             |                                                       |                                  \n                             |                                                       |                                  \n                   ---------------------                                   ---------------------                        \n                   |                   |                                   |                   |                        \n                   | consectetur adip  |                                   | Etiam laoreet co  |                        \n                   | iscing elit.      |---------------------              | ngue              |                        \n                   |                   |                    |              |                   |                        \n                   ---------------------                    |              ---------------------                        \n                      |                                     |                                                           \n                      |                                     |                                                           \n                      |                                     |                                                           \n                      |                                     |                                                           \n                      |                                     |                                                           \n            ---------------------                 ---------------------                                                 \n            |                   |                 |                   |                                                 \n            | Pellentesque fin  |                 | ante aliquet ull  |                                                 \n          --| ibus metus eget   |---              | amcorper          |                                                 \n          | |                   |  |              |                   |                                                 \n          | ---------------------  |              ---------------------                                                 \n          |                        |                                                                                    \n          |                        |                                                                                    \n          |                        |                                                                                    \n          |                        |                                                                                    \n          |                        |                                                                                    \n---------------------    ---------------------                                                                          \n|                   |    |                   |                                                                          \n| Sed nec nibh cur  |    | volutpat lacus a  |                                                                          \n| sus               |    | t, euismod mi     |                                                                          \n|                   |    |                   |                                                                          \n---------------------    ---------------------                                                                          \n```\n\nCurrently the screen width (the maximum character width assumed by the tree) is `180`.\nThis can be updated like:\n```\n>>> from ascii_tree import update_param\n>>> update_param('screen_width', 120)\n>>> make_and_print_tree(root, get_value, get_children)\n```\n\nThese params can be updated thus: `screen_width`, `margin` (distance between nodes),\n`padding` (distance between box contents and border) and `box_max_width`.\n\nSee more examples [here](./examples).\n\nThe [call_graph](https://github.com/spandanb/call_graph) library uses `ascii_tree` to print call graphs.\n\nNotes\n=====\n- Only supports `python3`\n\n- Boxes/nodes can have be upto max width, beyond which\ntheir contents are wrapped.\n\n- If a tree is too wide, then it is split at a node.\nPractically, this means that some children go one side\nof the split, and the rest on the other side.\nWhen a node is split, an information box is added as\na child to the node, which shows the page number where tree is continued.\n"
  },
  {
    "path": "ascii_tree/__init__.py",
    "content": "from .ascii_tree import print_tree\nfrom .external import transformed_tree, make_and_print_tree, update_param\n\n"
  },
  {
    "path": "ascii_tree/ascii_tree.py",
    "content": "'''\nConstruct ascii tree and determine placement of tree\nonto page or possibly onto multiple pages if it's too\nbig to fit on a single page.\n'''\n\nimport math\nfrom copy import copy\nfrom typing import List, Callable\nfrom . import charsets\nfrom .params import SCREEN_WIDTH, MARGIN, PADDING, SHOW_CONT_DIALOG\nfrom .custom_types import Node, Offset, AsciiBox\nfrom .draw import draw, draw_line\n\n\ndef get_margin(node_count: int, margin: int=MARGIN)->float:\n    '''\n    determine the amount of margin for node_count\n    '''\n    return margin*(node_count-1)\n\n\ndef recomp_node_width(root: Node, add_on: Node=None, margin: int=MARGIN)->int:\n    '''\n    Similar to get_node_widths, but only\n    computes the width of a single node.\n    Assumes children widths are computed\n    for non-leaf nodes.\n    Optionally accepts an `add_on`, i.e. a child\n    Node, not actually added to parent\n    '''\n    if root.is_leaf and add_on is None:\n        return root.box.width\n\n    if add_on:\n        total_width = sum(child.box.tree_width for child in root.children) + \\\n            + get_margin(len(root.children) + 1, margin) + add_on.box.tree_width\n    else:\n        total_width = sum(child.box.tree_width for child in root.children) + \\\n            + get_margin(len(root.children), margin)\n\n    total_width = max(total_width, root.box.width)\n    return total_width\n\n\ndef get_node_widths(root: Node, margin: int=MARGIN):\n    '''\n    - compute width of each node\n    - for a leaf node, this is width of encapsulating AsciiBox\n    - for a non-leaf node this is the width of the tree\n      rooted at node. this includes width between\n      children but not around them\n\n    Args:\n        margin: space between two nodes\n    Returns:\n        dict: map of width of each node.\n    '''\n    total_width = 0\n    if root.is_leaf:\n        total_width = root.box.width\n    else:\n        for child in root.children:\n          total_width += get_node_widths(child, margin)\n        # n children need n-1 spaces between them\n        total_width += get_margin(len(root.children), margin)\n        # in case parent is wider than all children\n        total_width = max(total_width, root.box.width)\n    root.box.tree_width = total_width\n    return total_width\n\n\ndef get_tree_height(root: Node, margin: int=MARGIN)->int:\n    '''\n    compute height of tree rooted at `root`.\n    this assumes nodes are not staggered\n    '''\n    stack = [(root, root.box.box_height)]\n    tree_height = 0\n    while stack:\n        node, height = stack.pop()\n        tree_height = max(tree_height, height)\n        for child in node.children:\n            stack.append((child, child.box.box_height + height + margin))\n    return tree_height\n\n\ndef position_nodes(root: Node, left_offset: int, top_offset: int, margin: int=MARGIN)-> Offset:\n    '''\n    compute top-left coordinate for each AsciiBox\n    update node.box.position in place\n    Args:\n        left_offset: (inclusive) left position;\n                    for root this is 0\n        top_offset\n    '''\n    if root.is_leaf:\n        root.box.position = Offset(left_offset, top_offset)\n    else:\n        # compute positions for children\n        # compute offsets for children\n        child_top_offset = top_offset + root.box.box_height + margin\n        for i, child in enumerate(root.children):\n            if i == 0:\n                child_left_offset = left_offset\n            else:\n                prev_child = root.children[i-1]\n                # using the offset of previous sibling is wrong since\n                # if the sibling has children, the sibling will be positioned\n                # in the middle of its children and its offset will shifted to right\n                # want the left offset of the leftmost descendent of the prev\n                # sibling. This can be achieved by directly storing that value\n                child_left_offset = prev_child.box.tree_left_offset + margin + prev_child.box.tree_width\n            position_nodes(child, child_left_offset, child_top_offset, margin)\n        # compute self positions\n        # place between first and last child\n        first = root.children[0].box.position.left\n        last = root.children[-1].box.position.left\n        node_left_offset = left_offset + (last - first) // 2\n        root.box.position = Offset(node_left_offset, top_offset)\n    # store the left-offset of the space for the box and it's descendents\n    root.box.tree_left_offset = left_offset\n    return root.box.position\n\n\ndef update_page_nums(page_map: dict, splits: List[Node]):\n    '''\n    page_map: maps root to msg_node, referencing it\n    '''\n    for i, split in enumerate(splits):\n        if split in page_map:\n            # relies on template string value\n            page_map[split].val = page_map[split].val.format(str(i))\n\n\ndef split_tree(root: Node, max_width: int=SCREEN_WIDTH, first_max_width: int=None,\n               margin: int=MARGIN, show_page: bool=SHOW_CONT_DIALOG):\n    '''\n    split a tree that is too wide/tall.\n    allows specifying a different first_max_width, e.g.\n    if node needs to be attached to parent, in some limited\n    space.\n    Whether to display page number is configurable, i.e. when the parent\n    node is distinct enough to visually track.\n    Splitting algorithm:\n        - keep list of splits\n        - clone root\n        - iteratively add a child (to this split) and determine\n          width (try with pagenum box if enabled)\n        - if tree gets too wide, split root\n        - if child is too wide, iteratively split child\n        - track page number\n    NB: typically have to fully copy a root at a split since\n    location and width info is stored directly on the objects.\n\n    TODO: implement turning off cont. on ... dialog\n    '''\n    if first_max_width is None:\n        first_max_width = max_width\n\n    # a child call will update a clone\n    # we may want to update actual, especially when slicing the tree\n    sroot = copy(root)\n    splits = []\n    child_splits = []  # inserted after splits on current level\n    # leave space for 3 digit page numbers\n    msg_node = Node.init_with_box('Cont. on page {}  ')\n    msg_node_copy = None\n    # maximum width a single child can consume\n    max_child_width = max_width - margin - msg_node.box.width\n    # map root to msg_node\n    page_map = {}\n    for i, child in enumerate(root.children):\n        if child.box.tree_width > max_child_width:\n            # recursively split child\n            csplits, cpage_map = split_tree(child, max_width=max_width, first_max_width=max_child_width,\n                                            margin=margin, show_page=show_page)\n            # will get attached to sroot\n            child = csplits[0]  # partitioned child\n            # keep separated so these splits can be inserted after siblings\n            child_splits.extend(csplits[1:])\n            page_map.update(cpage_map)\n\n        sroot.children.append(child)\n        is_last_child = i == len(root.children) - 1\n        if is_last_child:\n            new_width = recomp_node_width(sroot, margin=margin)\n        else:\n            new_width = recomp_node_width(sroot, add_on=msg_node, margin=margin)\n\n        # handle different first_max_width and max_width\n        if i == 0:\n            alloc_width = first_max_width\n        else:\n            alloc_width = max_width\n\n        if new_width > alloc_width:\n            sroot.children.pop()\n            msg_node_copy = copy(msg_node)\n            sroot.children.append(msg_node_copy)\n            # recompute widths\n            get_node_widths(sroot, margin)\n            position_nodes(sroot, 0, 0, margin)\n            splits.append(sroot)\n            # handle new childs\n            sroot = copy(sroot)\n            sroot.children.append(child)\n            page_map[sroot] = msg_node_copy\n\n    page_map[sroot] = msg_node_copy\n    get_node_widths(sroot)\n    position_nodes(sroot, 0, 0, margin)\n    splits.append(sroot)\n    splits.extend(child_splits)\n    return splits, page_map\n\n\ndef draw_tree(root, screen_width: int=SCREEN_WIDTH, margin: int=MARGIN, padding: int=PADDING,\n              charset=charsets.Unicode)->List[List[List[str]]]:\n    '''\n    Draw Ascii tree repr of root.\n    Return a list of screen objects with chunks of tree.\n    '''\n    screens = []\n    get_node_widths(root)\n    if root.box.tree_width <= screen_width:\n        # set screen height to be height of tree\n        screen_height = get_tree_height(root, margin)\n        # construct screen buffer\n        screen = [[' ']*screen_width for _ in range(screen_height)]\n        position_nodes(root, 0, 0, margin)\n        draw(screen, root, padding, charset)\n        screens.append(screen)\n    else:\n        # if tree is too wide, split the tree\n        splits, page_map = split_tree(root, max_width=screen_width, margin=margin)\n        update_page_nums(page_map, splits)\n        for sroot in splits:\n            screen_height = get_tree_height(sroot, margin)\n            screen = [[' ']*screen_width for _ in range(screen_height)]\n            draw(screen, sroot, padding, charset)\n            screens.append(screen)\n\n    return screens\n\n\ndef print_screen(screen):\n    '''\n    print each row of the screen\n    '''\n    for row in screen:\n        print(''.join(row).rstrip())\n\n\ndef print_tree(root: Node, screen_width: int=SCREEN_WIDTH, margin: int=MARGIN, padding: int=PADDING,\n               charset=charsets.Unicode):\n    '''\n    Output tree to stdout\n    '''\n    screens = draw_tree(root, screen_width, margin, padding, charset)\n    for i, screen in enumerate(screens):\n        print('page: {}'.format(i))\n        print_screen(screen)\n        # print page separator\n        if i != len(screens)-1:\n            print(draw_line(screen_width, charset))\n"
  },
  {
    "path": "ascii_tree/charsets.py",
    "content": "'''\ndifferent characters to draw the ascii tree with\n'''\n\nclass Charset:\n    def __init__(self, name=None, xside=None, yside=None, top=None, bottom=None, left=None, right=None,\n                 top_left=None, top_right=None, bottom_left=None, bottom_right=None, top_out=None,\n                 bottom_out=None, left_out=None, right_out=None):\n        '''\n        Accepts characters for different elements of the box.\n        Minimally, (xside, yside) or (top, left, bottom, right)\n        must be specified. The rest are intended to allow finer control.\n\n        xside: character to use for top and bottom, i.e. along x-axis\n        yside: character to use for left and right, i.e. along y-axis\n        top: character to use for box top. defaults to xside\n        bottom:\n        left: character to use for box left. defaults to yside\n        right:\n        top_left: character to use for top-left corner. defaults to xside\n        top_right:\n        bottom_left:\n        bottom_right:\n        left_out: character where an edge connects on the left. defaults to yside\n        right_out:\n        top_out: character where an edge connects on the left. defaults to xside\n        bottom_out:\n        '''\n        if (xside is None or yside is None) and \\\n           (left is None or right is None or top is None or bottom is None):\n            raise ValueError('Underdefined character set')\n\n        self.name = str(name)\n        self.xside = xside or top\n        self.yside = yside or left\n        self.top = top or xside\n        self.bottom = bottom or xside\n        self.left = left or yside\n        self.right = right or yside\n\n        self.top_left = top_left or xside\n        self.top_right = top_right or xside\n        self.bottom_left = bottom_left or xside\n        self.bottom_right = bottom_right or xside\n        self.top_out = top_out or xside\n        self.bottom_out = bottom_out or xside\n        self.left_out = left_out or yside\n        self.right_out = right_out or yside\n\n    def __str__(self):\n        return self.name\n\nAscii = Charset(name='ascii', xside='-', yside='|')\nUnicode = Charset(name='unicode', xside='─', yside='│', top_left='┌', top_right='┐', bottom_left='└', bottom_right='┘',\n                  left_out='┤', right_out='├', top_out='┴', bottom_out='┬')\n"
  },
  {
    "path": "ascii_tree/custom_types.py",
    "content": "import math\nfrom collections import namedtuple, deque\nfrom copy import copy\nfrom .params import PADDING, BOX_MAX_WIDTH\n\n# position offset\nOffset = namedtuple('Offset', 'left top')\n\n\nclass Node:\n    '''\n    Representing a node of the user tree\n    '''\n    def __init__(self, val):\n        self.val = str(val)\n        self.children = deque()\n        # reference to its AsciiBox\n        self.box = None\n\n    @classmethod\n    def init_with_box(cls, val, **kwargs):\n        '''\n        construct Node with associated AsciiBox\n        accepts kwargs to be passed to AsciiBox constructor\n        '''\n        node = cls(val)\n        node.box = AsciiBox(node.val, **kwargs)\n        return node\n\n    def __repr__(self):\n        return self.val\n\n    def __str__(self):\n        return self.val\n\n    @property\n    def is_leaf(self):\n        return len(self.children) == 0\n\n    def __copy__(self):\n        '''\n        utility to make a copy with\n        box copied and empty children\n        '''\n        clone = Node(self.val)\n        clone.box = copy(self.box)\n        return clone\n\n\nclass AsciiBox:\n    '''\n    Holds parameters associated with drawing\n    ascii box containing some text.\n    '''\n    def __init__(self, text: str, box_max_width: int=BOX_MAX_WIDTH, padding: int=PADDING):\n        self.text = text\n        # width of encapsulating box (includes border chars)\n        self.box_width = None\n        # numbers of chars line of text\n        self.line_width = None\n        # height of box\n        self.box_height = None\n        # height of contents\n        self.content_height = None\n        self.box_width, self.line_width, self.box_height, self.content_height = \\\n            self.box_dims(text, box_max_width, padding)\n        # width of tree rooted at node\n        # this must be updated for non-leaf nodes\n        self.tree_width = self.box_width\n        # top-left corner of this box's position\n        self.position = None\n        # left-offset of tree rooted at this node\n        # i.e. the left offset of rectangular box\n        # consumed by this node and it's descendents.\n        self.tree_left_offset = None\n\n    def __repr__(self):\n        return f'[{self.text}](bw:{self.box_width}, bh:{self.box_height}, cw:{self.line_width}, ch:{self.content_height})'\n\n    @property\n    def width(self):\n        return self.box_width\n\n    def box_dims(self, text: str, box_max_width: int, padding: int):\n        '''\n        determine the box dimensions, i.e.\n        box_width (width including border chars)\n        line_width (number of text chars)\n        box_height (height including border chars)\n        content_height (lines of wrapped text)\n        '''\n        # max text per line; each line has 2 paddings and 2 border chars\n        max_line_width = box_max_width - 2*padding - 2\n        if  max_line_width >= len(text):\n            box_width = len(text) + 2*padding + 2\n            line_width = len(text)\n        else:\n            # entire text won't fit on one line; wrap\n            box_width = box_max_width\n            line_width = max_line_width\n\n        content_height = math.ceil(len(text) / line_width)\n        # box height is height of content + 2 paddings + 2 border chars\n        box_height = content_height + 2*padding + 2\n\n        return box_width, line_width, box_height, content_height\n"
  },
  {
    "path": "ascii_tree/draw.py",
    "content": "'''\nFunctions for drawing\n'''\nfrom . import charsets\nfrom typing import List\nfrom .params import PADDING\nfrom .custom_types import Node, Offset\n\n\ndef draw_node(screen: list, text: str, left_bound: int, top_bound: int, box_width: int,\n              line_width: int, padding: int=PADDING, charset=charsets.Ascii):\n    '''\n    Update screen buffer with node ascii repr\n    '''\n    # inclusive left and right boundaries\n    right_bound = left_bound + box_width\n\n    # top border\n    for i in range(left_bound+1, right_bound):\n        screen[top_bound][i] = charset.top\n    # top-left corner\n    screen[top_bound][left_bound] = charset.top_left\n    # top-right corner\n    screen[top_bound][right_bound] = charset.top_right\n\n    # top padding\n    row_idx = top_bound + 1\n    for p in range(padding):\n        for i in range(left_bound, right_bound+1):\n            if i == left_bound:\n                screen[row_idx + p][i] = charset.left\n            elif i == right_bound:\n                screen[row_idx + p][i] = charset.right\n\n    # create body of box\n    # each row will contain box_width total characters\n    # and line_width text chars\n    row_idx += padding\n    idx = 0  # iterates over chars in text\n    while idx < len(text):\n        # left boundary\n        screen[row_idx][left_bound] = charset.left\n        # left padding\n        for i in range(padding):\n            screen[row_idx][left_bound + i + 1] = ' '\n        # content\n        subtext = text[idx:idx + line_width]\n        text_lbound = left_bound + padding + 1\n        screen[row_idx][text_lbound: text_lbound+len(subtext)] = subtext\n        # right padding\n        for i in range(padding):\n            screen[row_idx][right_bound - i - 1] = ' '\n        # right boundary\n        screen[row_idx][right_bound] = charset.right\n        idx += line_width\n        row_idx += 1\n\n    # bottom padding\n    for p in range(padding):\n        for i in range(left_bound, right_bound+1):\n            if i == left_bound:\n                screen[row_idx + p][i] = charset.left\n            elif i == right_bound:\n                screen[row_idx + p][i] = charset.right\n\n    row_idx += padding\n\n    bottom_bound = row_idx\n    # bottom-left corner\n    screen[bottom_bound][left_bound] = charset.bottom_left\n    # bottom-right corner\n    screen[bottom_bound][right_bound] = charset.bottom_right\n    # bottom border\n    for i in range(left_bound+1, right_bound):\n        screen[bottom_bound][i] = charset.bottom\n\n\ndef draw_edge(screen, src: Node, dest: Node, charset=charsets.Ascii):\n    '''\n    draw an edge between src and destination nodes.\n    an edge always extends from the side/bottom of src node and goes to the\n    middle of the of dest node\n    '''\n    ascii_src = src.box\n    ascii_dest = dest.box\n    # for dest, edge will connect in middle of top\n    dest_x = ascii_dest.position.left + ascii_dest.box_width // 2\n    dest_y = ascii_dest.position.top\n\n    src_lbound = ascii_src.position.left\n    src_rbound = ascii_src.position.left + ascii_src.box_width\n    src_ymiddle = ascii_src.position.top + ascii_src.box_height // 2\n    # based on entry point on dest, determine whether\n    # the line will go from left, right, or center\n    # of source\n\n    if src_lbound > dest_x:\n        # edge will go from left of source box\n        # y-axis of where to extend from source\n        # draw horizontal leg\n        for i in range(src_lbound, dest_x-1, -1):\n            screen[src_ymiddle][i] = charset.xside\n        # draw left out going portrusion\n        screen[src_ymiddle][src_lbound] = charset.left_out\n        # draw elbow of edge\n        screen[src_ymiddle][dest_x] = charset.top_left\n        # draw vertical leg\n        for i in range(src_ymiddle+1, dest_y):\n            screen[i][dest_x] = charset.yside\n    elif src_rbound < dest_x:\n        # edge will go from right of source box\n        for i in range(src_rbound, dest_x+1):\n            screen[src_ymiddle][i] = charset.xside\n        # right protrusion\n        screen[src_ymiddle][src_rbound] = charset.right_out\n        # elbow\n        screen[src_ymiddle][dest_x] = charset.top_right\n        # then draw vertical leg\n        for i in range(src_ymiddle+1, dest_y):\n            screen[i][dest_x] = charset.yside\n    else:\n        # edge will go from underneath source box\n        src_ybottom = ascii_src.box_height + ascii_src.position.top\n        for i in range(src_ybottom, dest_y):\n            screen[i][dest_x] = charset.yside\n        # bottom protrusion - goes on box which is one unit\n        # higher hence -1\n        screen[src_ybottom-1][dest_x] = charset.bottom_out\n\n    # every dest box will have a top out\n    screen[dest_y][dest_x] = charset.top_out\n\n\ndef draw(screen: List[List[str]], root: Node, padding: int=PADDING, charset=charsets.Unicode):\n    '''\n    traverse tree and draw nodes and edges\n    '''\n    stack = [root]\n    edges = []\n    while stack:\n        node = stack.pop()\n        left_bound, top_bound = node.box.position\n        box_width, line_width = node.box.box_width, node.box.line_width\n        draw_node(screen, node.val, left_bound, top_bound, box_width, line_width, padding, charset)\n        for child in node.children:\n            # order doesn't matter since each node is drawn independently\n            stack.append(child)\n            edges.append((node, child))\n    # draw edges at the end since draw_edge handles\n    # adding outgoing protrusion chars on box\n    for node, child in edges:\n        draw_edge(screen, node, child, charset)\n\n\ndef draw_line(width, charset=charsets.Unicode)->str:\n    '''\n    draw line\n    '''\n    return charset.xside*width\n"
  },
  {
    "path": "ascii_tree/external.py",
    "content": "'''\nUtilities for external interactions\n'''\nfrom . import charsets\nfrom typing import Callable, Any, List\nfrom .ascii_tree import print_tree\nfrom .custom_types import Node\n\n\n# params may overlap\nSCREEN_PARAM_NAMES = ['screen_width', 'margin', 'padding', 'charset']\nBOX_PARAM_NAMES = ['padding', 'box_max_width']\n# updated param values\n_screen_params = {}\n_box_params = {}\n\n\ndef transformed_tree(root: Any, get_val: Callable[[Any], Any], get_children: Callable[[Any], List])->Node:\n    '''\n    Utility func\n    transform tree from arbitrary node type\n    to tree of `Node`. This is needed since\n    functions here assume the Node structure.\n    `get_val` and `get_children` are callables that when called\n    on source node, return val and children (list) respectively.\n    '''\n    troot = Node.init_with_box(get_val(root), **_box_params)\n    troot.children = [transformed_tree(child, get_val, get_children) for child in get_children(root)]\n    return troot\n\n\ndef make_and_print_tree(root: Any, get_val: Callable[[Any], Any], get_children: Callable[[Any], List]):\n    '''\n    Utility method that transforms and prints tree(s)\n    '''\n    print_tree(transformed_tree(root, get_val, get_children), **_screen_params)\n\n\ndef transform_param(param, val):\n    '''\n    some param value need to be transformed\n    '''\n    if param == 'charset':\n        if val.lower() == 'unicode':\n            return charsets.Unicode\n        return charsets.Ascii\n\n    return val\n\n\ndef update_param(param: str, new_val):\n    '''\n    updates param dict\n    '''\n    param = param.lower()\n    new_val = transform_param(param, new_val)\n    if param in SCREEN_PARAM_NAMES:\n        _screen_params[param] = new_val\n        print(f'{param} updated to {new_val}')\n    if param in BOX_PARAM_NAMES:\n        _box_params[param] = new_val\n        print(f'{param} updated to {new_val}')\n"
  },
  {
    "path": "ascii_tree/params.py",
    "content": "SCREEN_WIDTH = 180\nMARGIN = 5  # space between nodes\nPADDING = 1  # space between node body and node walls\nBOX_MAX_WIDTH = 30  # maximum width of ascii box\nSHOW_CONT_DIALOG = True\n"
  },
  {
    "path": "examples/big_tree.py",
    "content": "'''\nIf the tree can't fit on one screen, it will be split into\npages and printed over multiple pages.\nFirst let's construct a big tree.\n'''\nfrom ascii_tree import make_and_print_tree\nfrom node_types import NTreeNode\n\n\n'''\nFirst generate a really wide tree\n'''\n\nroot0 = NTreeNode('onomatopaie'*10)\nroot0.children = [NTreeNode('boobar'), NTreeNode('car'), NTreeNode('scuba'), NTreeNode('tarzanman'), NTreeNode('smartcar')]\nroot0.children[-1].children = [NTreeNode('Kathmandu'), NTreeNode('smartcat '*20), NTreeNode('lorem ipsum dolor...')]\nroot0.children[-1].children[0].children = [NTreeNode('fooball '*10)]\nroot0.children[-1].children[1].children = [NTreeNode('foosbag')]\nroot0.children.append(NTreeNode('carbar'))\nprint(\"Printing root0...\")\n\nmake_and_print_tree(root0, lambda node: node.val, lambda node:node.children)\n"
  },
  {
    "path": "examples/big_tree1.py",
    "content": "from ascii_tree import make_and_print_tree\nfrom node_types import  NTreeNode\n\n'''\nNow make it ever wider, such that it can't fit on the screen.\nThis will print multiple pages now\n'''\nroot1 = NTreeNode('onomatopaie'*10)\nroot1.children = [NTreeNode(f'lorem ipsum dolor...{i}') for i in range(10)]\nprint(\"Printing root1...\")\nmake_and_print_tree(root1, lambda node: node.val, lambda node:node.children)\n\n'''\nNotice how the object is split over multiple pages.\nAlso observer that the parent is duplicated in both pages.\nThis is done to make the graph more readable.\nThe splitting algorithm will split along any level of\nthe tree.\n'''\n"
  },
  {
    "path": "examples/big_tree2.py",
    "content": "from ascii_tree import make_and_print_tree\nfrom node_types import NTreeNode\n\nroot2 = NTreeNode('onomatopaie'*10)\nroot2.children = [NTreeNode('boobar'), NTreeNode('car'), NTreeNode('scuba'), NTreeNode('tarzanman'), NTreeNode('smartcar')]\nroot2.children[-1].children = [NTreeNode('Kathmandu'*5), NTreeNode('smartcat '*20), NTreeNode('lorem ipsum dolor...'*20)]\nroot2.children[-1].children.extend([NTreeNode('Kathmandu'*5), NTreeNode('smartcat '*20), NTreeNode('lorem ipsum dolor...'*20)])\nroot2.children[-1].children[0].children = [NTreeNode('fooball '*10)]\nroot2.children[-1].children[1].children = [NTreeNode('foosbag')]\nroot2.children.append(NTreeNode('carbar'))\nprint(\"Printing root2...\")\nmake_and_print_tree(root2, lambda node: node.val, lambda node:node.children)\n"
  },
  {
    "path": "examples/change_config.py",
    "content": "'''\nIf the tree can't fit on one screen, it will be split into\npages and printed over multiple pages.\nFirst let's construct a big tree.\n'''\nfrom ascii_tree import make_and_print_tree, update_param\n\n\nclass NTreeNode:\n    def __init__(self, val):\n        self.val = val\n        self.children = []\n\n'''\nFirst generate a really wide tree\n'''\n\nroot0 = NTreeNode('onomatopaie'*10)\nroot0.children = [NTreeNode('boobar'), NTreeNode('car'), NTreeNode('scuba'), NTreeNode('tarzanman'), NTreeNode('smartcar')]\nroot0.children[-1].children = [NTreeNode('Kathmandu'), NTreeNode('smartcat '*20), NTreeNode('lorem ipsum dolor...')]\nroot0.children[-1].children[0].children = [NTreeNode('fooball '*10)]\nroot0.children[-1].children[1].children = [NTreeNode('foosbag')]\nroot0.children.append(NTreeNode('carbar'))\n\nupdate_param('box_max_width', 20)\nupdate_param('screen_width', 100)\n\nprint(\"Printing root0...\")\n\nmake_and_print_tree(root0, lambda node: node.val, lambda node:node.children)\n"
  },
  {
    "path": "examples/node_types.py",
    "content": "\nclass BinaryTreeNode:\n    def __init__(self, value):\n        self.value = value\n        self.left = None\n        self.right = None\n\n\nclass NTreeNode:\n    def __init__(self, val):\n        self.val = val\n        self.children = []\n\n"
  },
  {
    "path": "examples/small_tree.py",
    "content": "'''\nAscii Tree is library for printing beautiful ascii trees.\nThe first step is to convert the tree into another\ntree that is comrehensible by the library.\nFor this we'll use the following.\n'''\n\nfrom ascii_tree import transformed_tree, print_tree\nfrom node_types import BinaryTreeNode\n\n\n'''\nconstruct a dummy tre\n'''\nroot = BinaryTreeNode(1)\nroot.left = BinaryTreeNode(2)\nroot.right = BinaryTreeNode(3)\nroot.left.left = BinaryTreeNode(4)\nroot.left.right = BinaryTreeNode(5)\nroot.right.left = BinaryTreeNode(6)\nroot.right.right = BinaryTreeNode(7)\n\n'''\ntransformed_tree(root, get_val: Callable, get_children: Callable):\ntransformed_tree accepts the root node of some arbitrary tree class,\na callable, called with the node as the argument to get its value\nand another callable to get the current node's children\n'''\n\ndef get_val(node):\n    '''\n    get value from node\n    '''\n    return node.value\n\n\ndef get_children(node):\n    '''\n    get list of children from node\n    '''\n    children = []\n    if node.left:\n        children.append(node.left)\n    if node.right:\n        children.append(node.right)\n    return children\n\n\ntroot = transformed_tree(root, get_val, get_children)\nprint_tree(troot)\n'''\npage: 0\n          ------\n          |    |\n       ---- 1  -------------\n       |  |    |           |\n       |  ------           |\n       |                   |\n       |                   |\n       |                   |\n       |                   |\n       |                   |\n     ------              ------\n     |    |              |    |\n  ---- 2  ---         ---- 3  ---\n  |  |    | |         |  |    | |\n  |  ------ |         |  ------ |\n  |         |         |         |\n  |         |         |         |\n  |         |         |         |\n  |         |         |         |\n  |         |         |         |\n------    ------    ------    ------\n|    |    |    |    |    |    |    |\n| 4  |    | 5  |    | 6  |    | 7  |\n|    |    |    |    |    |    |    |\n------    ------    ------    ------\n'''\n"
  },
  {
    "path": "examples/small_tree1.py",
    "content": "from ascii_tree import make_and_print_tree, update_param\nfrom node_types import NTreeNode\n\nroot = NTreeNode('Lorem ipsum dolor sit amet')\nroot.children = [NTreeNode('consectetur adipiscing elit.'), NTreeNode('Etiam laoreet congue')]\nroot.children[0].children = [NTreeNode('Pellentesque finibus metus eget'), NTreeNode('ante aliquet ullamcorper')]\nroot.children[0].children[0].children = [NTreeNode('Sed nec nibh cursus'),  NTreeNode('volutpat lacus at, euismod mi')]\n\nupdate_param('screen_width', 120)\nupdate_param('box_max_width', 20)\nmake_and_print_tree(root, lambda n: n.val, lambda n:n.children)\n"
  },
  {
    "path": "examples/small_tree2.py",
    "content": "from ascii_tree import make_and_print_tree, update_param\nfrom node_types import NTreeNode\n\nroot = NTreeNode('Lorem ipsum dolor sit amet')\nroot.children = [NTreeNode('consectetur adipiscing elit.'), NTreeNode('Etiam laoreet congue')]\nroot.children[0].children = [NTreeNode('Pellentesque finibus metus eget'), NTreeNode('ante aliquet ullamcorper')]\nroot.children[1].children = [NTreeNode('Morbi porta, diam at imperdiet venenatis'),  NTreeNode('neque eros bibendum tortor, quis')]\n\nupdate_param('screen_width', 120)\nupdate_param('box_max_width', 20)\nupdate_param('charset', 'ascii')\nmake_and_print_tree(root, lambda n: n.val, lambda n:n.children)\n"
  },
  {
    "path": "setup.py",
    "content": "from setuptools import setup, find_packages\n\n\nsetup(name='ascii_tree',\n      version='0.1',\n      description='create beautiful ascii trees',\n      python_requires='>=3',\n      packages=find_packages()\n)\n"
  }
]