Repository: spandanb/ascii_tree
Branch: master
Commit: c8a902e4e839
Files: 18
Total size: 42.2 KB
Directory structure:
gitextract_z82j96jg/
├── LICENSE
├── README.md
├── ascii_tree/
│ ├── __init__.py
│ ├── ascii_tree.py
│ ├── charsets.py
│ ├── custom_types.py
│ ├── draw.py
│ ├── external.py
│ └── params.py
├── examples/
│ ├── big_tree.py
│ ├── big_tree1.py
│ ├── big_tree2.py
│ ├── change_config.py
│ ├── node_types.py
│ ├── small_tree.py
│ ├── small_tree1.py
│ └── small_tree2.py
└── setup.py
================================================
FILE CONTENTS
================================================
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) [2019] [Spandan Bemby]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
Ascii Tree
==========
Generate beautiful ascii trees.
```
I think that I shall never see
A graph more lovely than a tree.
A tree whose crucial property
Is loop-free connectivity.
A tree that must be sure to span
So packets can reach every LAN.
First, the root must be selected.
By ID, it is elected.
Least-cost paths from root are traced.
In the tree, these paths are placed.
A mesh is made by folks like me,
Then bridges find a spanning tree.
-Radia Perlman
```
Installation
============
`git clone ...`
`cd ...`
`python3 setup.py install`
Usage
=====
This library can print **arbitrary** trees. This requires
you to specify how the value of a node, and list
of it's children can be extracted from the node object.
For example, consider the following n-ary tree node class
```
>>> class NTreeNode:
... def __init__(self, val):
... self.val = val
... self.children = []
```
We can extract it's value and children like
```
>>> def get_value(node):
... return node.value
...
>>> def get_children(node):
... return node.children
...
```
First, we'll construct a dummy tree.
```
>>> root = NTreeNode('Lorem ipsum dolor sit amet')
>>> root.children = [NTreeNode('consectetur adipiscing elit.'), NTreeNode('Etiam laoreet congue')]
>>> root.children[0].children = [NTreeNode('Pellentesque finibus metus eget'), NTreeNode('ante aliquet ullamcorper')]
>>> root.children[1].children = [NTreeNode('Morbi porta, diam at imperdiet venenatis'), NTreeNode('neque eros bibendum tortor, quis')]
```
Now we can print this as an ascii (the library originally only used ascii characters) tree like
```
>>> from ascii_tree import make_and_print_tree
>>> make_and_print_tree(root, get_value, get_children)
page: 0
┌───────────────────┐
│ │
│ Lorem ipsum dolo │
│ r sit amet ├────────────────────────────────────┐
│ │ │
└┬──────────────────┘ │
│ │
│ │
│ │
│ │
│ │
┌─────────┴─────────┐ ┌─────────┴─────────┐
│ │ │ │
│ consectetur adip │ │ Etiam laoreet co │
│ iscing elit. ├────────────────────┐ │ ngue │
│ │ │ │ │
└──┬────────────────┘ │ └───────────────────┘
│ │
│ │
│ │
│ │
│ │
┌─────────┴─────────┐ ┌─────────┴─────────┐
│ │ │ │
│ Pellentesque fin │ │ ante aliquet ull │
┌─┤ ibus metus eget ├──┐ │ amcorper │
│ │ │ │ │ │
│ └───────────────────┘ │ └───────────────────┘
│ │
│ │
│ │
│ │
│ │
┌─────────┴─────────┐ ┌─────────┴─────────┐
│ │ │ │
│ Sed nec nibh cur │ │ volutpat lacus a │
│ sus │ │ t, euismod mi │
│ │ │ │
└───────────────────┘ └───────────────────┘
```
We can also print the tree using only ascii characters like:
```
>>> update_param('charset', 'ascii')
>>> make_and_print_tree(root, lambda n: n.val, lambda n: n.children)
page: 0
---------------------
| |
| Lorem ipsum dolo |
| r sit amet |-------------------------------------
| | |
--------------------- |
| |
| |
| |
| |
| |
--------------------- ---------------------
| | | |
| consectetur adip | | Etiam laoreet co |
| iscing elit. |--------------------- | ngue |
| | | | |
--------------------- | ---------------------
| |
| |
| |
| |
| |
--------------------- ---------------------
| | | |
| Pellentesque fin | | ante aliquet ull |
--| ibus metus eget |--- | amcorper |
| | | | | |
| --------------------- | ---------------------
| |
| |
| |
| |
| |
--------------------- ---------------------
| | | |
| Sed nec nibh cur | | volutpat lacus a |
| sus | | t, euismod mi |
| | | |
--------------------- ---------------------
```
Currently the screen width (the maximum character width assumed by the tree) is `180`.
This can be updated like:
```
>>> from ascii_tree import update_param
>>> update_param('screen_width', 120)
>>> make_and_print_tree(root, get_value, get_children)
```
These params can be updated thus: `screen_width`, `margin` (distance between nodes),
`padding` (distance between box contents and border) and `box_max_width`.
See more examples [here](./examples).
The [call_graph](https://github.com/spandanb/call_graph) library uses `ascii_tree` to print call graphs.
Notes
=====
- Only supports `python3`
- Boxes/nodes can have be upto max width, beyond which
their contents are wrapped.
- If a tree is too wide, then it is split at a node.
Practically, this means that some children go one side
of the split, and the rest on the other side.
When a node is split, an information box is added as
a child to the node, which shows the page number where tree is continued.
================================================
FILE: ascii_tree/__init__.py
================================================
from .ascii_tree import print_tree
from .external import transformed_tree, make_and_print_tree, update_param
================================================
FILE: ascii_tree/ascii_tree.py
================================================
'''
Construct ascii tree and determine placement of tree
onto page or possibly onto multiple pages if it's too
big to fit on a single page.
'''
import math
from copy import copy
from typing import List, Callable
from . import charsets
from .params import SCREEN_WIDTH, MARGIN, PADDING, SHOW_CONT_DIALOG
from .custom_types import Node, Offset, AsciiBox
from .draw import draw, draw_line
def get_margin(node_count: int, margin: int=MARGIN)->float:
'''
determine the amount of margin for node_count
'''
return margin*(node_count-1)
def recomp_node_width(root: Node, add_on: Node=None, margin: int=MARGIN)->int:
'''
Similar to get_node_widths, but only
computes the width of a single node.
Assumes children widths are computed
for non-leaf nodes.
Optionally accepts an `add_on`, i.e. a child
Node, not actually added to parent
'''
if root.is_leaf and add_on is None:
return root.box.width
if add_on:
total_width = sum(child.box.tree_width for child in root.children) + \
+ get_margin(len(root.children) + 1, margin) + add_on.box.tree_width
else:
total_width = sum(child.box.tree_width for child in root.children) + \
+ get_margin(len(root.children), margin)
total_width = max(total_width, root.box.width)
return total_width
def get_node_widths(root: Node, margin: int=MARGIN):
'''
- compute width of each node
- for a leaf node, this is width of encapsulating AsciiBox
- for a non-leaf node this is the width of the tree
rooted at node. this includes width between
children but not around them
Args:
margin: space between two nodes
Returns:
dict: map of width of each node.
'''
total_width = 0
if root.is_leaf:
total_width = root.box.width
else:
for child in root.children:
total_width += get_node_widths(child, margin)
# n children need n-1 spaces between them
total_width += get_margin(len(root.children), margin)
# in case parent is wider than all children
total_width = max(total_width, root.box.width)
root.box.tree_width = total_width
return total_width
def get_tree_height(root: Node, margin: int=MARGIN)->int:
'''
compute height of tree rooted at `root`.
this assumes nodes are not staggered
'''
stack = [(root, root.box.box_height)]
tree_height = 0
while stack:
node, height = stack.pop()
tree_height = max(tree_height, height)
for child in node.children:
stack.append((child, child.box.box_height + height + margin))
return tree_height
def position_nodes(root: Node, left_offset: int, top_offset: int, margin: int=MARGIN)-> Offset:
'''
compute top-left coordinate for each AsciiBox
update node.box.position in place
Args:
left_offset: (inclusive) left position;
for root this is 0
top_offset
'''
if root.is_leaf:
root.box.position = Offset(left_offset, top_offset)
else:
# compute positions for children
# compute offsets for children
child_top_offset = top_offset + root.box.box_height + margin
for i, child in enumerate(root.children):
if i == 0:
child_left_offset = left_offset
else:
prev_child = root.children[i-1]
# using the offset of previous sibling is wrong since
# if the sibling has children, the sibling will be positioned
# in the middle of its children and its offset will shifted to right
# want the left offset of the leftmost descendent of the prev
# sibling. This can be achieved by directly storing that value
child_left_offset = prev_child.box.tree_left_offset + margin + prev_child.box.tree_width
position_nodes(child, child_left_offset, child_top_offset, margin)
# compute self positions
# place between first and last child
first = root.children[0].box.position.left
last = root.children[-1].box.position.left
node_left_offset = left_offset + (last - first) // 2
root.box.position = Offset(node_left_offset, top_offset)
# store the left-offset of the space for the box and it's descendents
root.box.tree_left_offset = left_offset
return root.box.position
def update_page_nums(page_map: dict, splits: List[Node]):
'''
page_map: maps root to msg_node, referencing it
'''
for i, split in enumerate(splits):
if split in page_map:
# relies on template string value
page_map[split].val = page_map[split].val.format(str(i))
def split_tree(root: Node, max_width: int=SCREEN_WIDTH, first_max_width: int=None,
margin: int=MARGIN, show_page: bool=SHOW_CONT_DIALOG):
'''
split a tree that is too wide/tall.
allows specifying a different first_max_width, e.g.
if node needs to be attached to parent, in some limited
space.
Whether to display page number is configurable, i.e. when the parent
node is distinct enough to visually track.
Splitting algorithm:
- keep list of splits
- clone root
- iteratively add a child (to this split) and determine
width (try with pagenum box if enabled)
- if tree gets too wide, split root
- if child is too wide, iteratively split child
- track page number
NB: typically have to fully copy a root at a split since
location and width info is stored directly on the objects.
TODO: implement turning off cont. on ... dialog
'''
if first_max_width is None:
first_max_width = max_width
# a child call will update a clone
# we may want to update actual, especially when slicing the tree
sroot = copy(root)
splits = []
child_splits = [] # inserted after splits on current level
# leave space for 3 digit page numbers
msg_node = Node.init_with_box('Cont. on page {} ')
msg_node_copy = None
# maximum width a single child can consume
max_child_width = max_width - margin - msg_node.box.width
# map root to msg_node
page_map = {}
for i, child in enumerate(root.children):
if child.box.tree_width > max_child_width:
# recursively split child
csplits, cpage_map = split_tree(child, max_width=max_width, first_max_width=max_child_width,
margin=margin, show_page=show_page)
# will get attached to sroot
child = csplits[0] # partitioned child
# keep separated so these splits can be inserted after siblings
child_splits.extend(csplits[1:])
page_map.update(cpage_map)
sroot.children.append(child)
is_last_child = i == len(root.children) - 1
if is_last_child:
new_width = recomp_node_width(sroot, margin=margin)
else:
new_width = recomp_node_width(sroot, add_on=msg_node, margin=margin)
# handle different first_max_width and max_width
if i == 0:
alloc_width = first_max_width
else:
alloc_width = max_width
if new_width > alloc_width:
sroot.children.pop()
msg_node_copy = copy(msg_node)
sroot.children.append(msg_node_copy)
# recompute widths
get_node_widths(sroot, margin)
position_nodes(sroot, 0, 0, margin)
splits.append(sroot)
# handle new childs
sroot = copy(sroot)
sroot.children.append(child)
page_map[sroot] = msg_node_copy
page_map[sroot] = msg_node_copy
get_node_widths(sroot)
position_nodes(sroot, 0, 0, margin)
splits.append(sroot)
splits.extend(child_splits)
return splits, page_map
def draw_tree(root, screen_width: int=SCREEN_WIDTH, margin: int=MARGIN, padding: int=PADDING,
charset=charsets.Unicode)->List[List[List[str]]]:
'''
Draw Ascii tree repr of root.
Return a list of screen objects with chunks of tree.
'''
screens = []
get_node_widths(root)
if root.box.tree_width <= screen_width:
# set screen height to be height of tree
screen_height = get_tree_height(root, margin)
# construct screen buffer
screen = [[' ']*screen_width for _ in range(screen_height)]
position_nodes(root, 0, 0, margin)
draw(screen, root, padding, charset)
screens.append(screen)
else:
# if tree is too wide, split the tree
splits, page_map = split_tree(root, max_width=screen_width, margin=margin)
update_page_nums(page_map, splits)
for sroot in splits:
screen_height = get_tree_height(sroot, margin)
screen = [[' ']*screen_width for _ in range(screen_height)]
draw(screen, sroot, padding, charset)
screens.append(screen)
return screens
def print_screen(screen):
'''
print each row of the screen
'''
for row in screen:
print(''.join(row).rstrip())
def print_tree(root: Node, screen_width: int=SCREEN_WIDTH, margin: int=MARGIN, padding: int=PADDING,
charset=charsets.Unicode):
'''
Output tree to stdout
'''
screens = draw_tree(root, screen_width, margin, padding, charset)
for i, screen in enumerate(screens):
print('page: {}'.format(i))
print_screen(screen)
# print page separator
if i != len(screens)-1:
print(draw_line(screen_width, charset))
================================================
FILE: ascii_tree/charsets.py
================================================
'''
different characters to draw the ascii tree with
'''
class Charset:
def __init__(self, name=None, xside=None, yside=None, top=None, bottom=None, left=None, right=None,
top_left=None, top_right=None, bottom_left=None, bottom_right=None, top_out=None,
bottom_out=None, left_out=None, right_out=None):
'''
Accepts characters for different elements of the box.
Minimally, (xside, yside) or (top, left, bottom, right)
must be specified. The rest are intended to allow finer control.
xside: character to use for top and bottom, i.e. along x-axis
yside: character to use for left and right, i.e. along y-axis
top: character to use for box top. defaults to xside
bottom:
left: character to use for box left. defaults to yside
right:
top_left: character to use for top-left corner. defaults to xside
top_right:
bottom_left:
bottom_right:
left_out: character where an edge connects on the left. defaults to yside
right_out:
top_out: character where an edge connects on the left. defaults to xside
bottom_out:
'''
if (xside is None or yside is None) and \
(left is None or right is None or top is None or bottom is None):
raise ValueError('Underdefined character set')
self.name = str(name)
self.xside = xside or top
self.yside = yside or left
self.top = top or xside
self.bottom = bottom or xside
self.left = left or yside
self.right = right or yside
self.top_left = top_left or xside
self.top_right = top_right or xside
self.bottom_left = bottom_left or xside
self.bottom_right = bottom_right or xside
self.top_out = top_out or xside
self.bottom_out = bottom_out or xside
self.left_out = left_out or yside
self.right_out = right_out or yside
def __str__(self):
return self.name
Ascii = Charset(name='ascii', xside='-', yside='|')
Unicode = Charset(name='unicode', xside='─', yside='│', top_left='┌', top_right='┐', bottom_left='└', bottom_right='┘',
left_out='┤', right_out='├', top_out='┴', bottom_out='┬')
================================================
FILE: ascii_tree/custom_types.py
================================================
import math
from collections import namedtuple, deque
from copy import copy
from .params import PADDING, BOX_MAX_WIDTH
# position offset
Offset = namedtuple('Offset', 'left top')
class Node:
'''
Representing a node of the user tree
'''
def __init__(self, val):
self.val = str(val)
self.children = deque()
# reference to its AsciiBox
self.box = None
@classmethod
def init_with_box(cls, val, **kwargs):
'''
construct Node with associated AsciiBox
accepts kwargs to be passed to AsciiBox constructor
'''
node = cls(val)
node.box = AsciiBox(node.val, **kwargs)
return node
def __repr__(self):
return self.val
def __str__(self):
return self.val
@property
def is_leaf(self):
return len(self.children) == 0
def __copy__(self):
'''
utility to make a copy with
box copied and empty children
'''
clone = Node(self.val)
clone.box = copy(self.box)
return clone
class AsciiBox:
'''
Holds parameters associated with drawing
ascii box containing some text.
'''
def __init__(self, text: str, box_max_width: int=BOX_MAX_WIDTH, padding: int=PADDING):
self.text = text
# width of encapsulating box (includes border chars)
self.box_width = None
# numbers of chars line of text
self.line_width = None
# height of box
self.box_height = None
# height of contents
self.content_height = None
self.box_width, self.line_width, self.box_height, self.content_height = \
self.box_dims(text, box_max_width, padding)
# width of tree rooted at node
# this must be updated for non-leaf nodes
self.tree_width = self.box_width
# top-left corner of this box's position
self.position = None
# left-offset of tree rooted at this node
# i.e. the left offset of rectangular box
# consumed by this node and it's descendents.
self.tree_left_offset = None
def __repr__(self):
return f'[{self.text}](bw:{self.box_width}, bh:{self.box_height}, cw:{self.line_width}, ch:{self.content_height})'
@property
def width(self):
return self.box_width
def box_dims(self, text: str, box_max_width: int, padding: int):
'''
determine the box dimensions, i.e.
box_width (width including border chars)
line_width (number of text chars)
box_height (height including border chars)
content_height (lines of wrapped text)
'''
# max text per line; each line has 2 paddings and 2 border chars
max_line_width = box_max_width - 2*padding - 2
if max_line_width >= len(text):
box_width = len(text) + 2*padding + 2
line_width = len(text)
else:
# entire text won't fit on one line; wrap
box_width = box_max_width
line_width = max_line_width
content_height = math.ceil(len(text) / line_width)
# box height is height of content + 2 paddings + 2 border chars
box_height = content_height + 2*padding + 2
return box_width, line_width, box_height, content_height
================================================
FILE: ascii_tree/draw.py
================================================
'''
Functions for drawing
'''
from . import charsets
from typing import List
from .params import PADDING
from .custom_types import Node, Offset
def draw_node(screen: list, text: str, left_bound: int, top_bound: int, box_width: int,
line_width: int, padding: int=PADDING, charset=charsets.Ascii):
'''
Update screen buffer with node ascii repr
'''
# inclusive left and right boundaries
right_bound = left_bound + box_width
# top border
for i in range(left_bound+1, right_bound):
screen[top_bound][i] = charset.top
# top-left corner
screen[top_bound][left_bound] = charset.top_left
# top-right corner
screen[top_bound][right_bound] = charset.top_right
# top padding
row_idx = top_bound + 1
for p in range(padding):
for i in range(left_bound, right_bound+1):
if i == left_bound:
screen[row_idx + p][i] = charset.left
elif i == right_bound:
screen[row_idx + p][i] = charset.right
# create body of box
# each row will contain box_width total characters
# and line_width text chars
row_idx += padding
idx = 0 # iterates over chars in text
while idx < len(text):
# left boundary
screen[row_idx][left_bound] = charset.left
# left padding
for i in range(padding):
screen[row_idx][left_bound + i + 1] = ' '
# content
subtext = text[idx:idx + line_width]
text_lbound = left_bound + padding + 1
screen[row_idx][text_lbound: text_lbound+len(subtext)] = subtext
# right padding
for i in range(padding):
screen[row_idx][right_bound - i - 1] = ' '
# right boundary
screen[row_idx][right_bound] = charset.right
idx += line_width
row_idx += 1
# bottom padding
for p in range(padding):
for i in range(left_bound, right_bound+1):
if i == left_bound:
screen[row_idx + p][i] = charset.left
elif i == right_bound:
screen[row_idx + p][i] = charset.right
row_idx += padding
bottom_bound = row_idx
# bottom-left corner
screen[bottom_bound][left_bound] = charset.bottom_left
# bottom-right corner
screen[bottom_bound][right_bound] = charset.bottom_right
# bottom border
for i in range(left_bound+1, right_bound):
screen[bottom_bound][i] = charset.bottom
def draw_edge(screen, src: Node, dest: Node, charset=charsets.Ascii):
'''
draw an edge between src and destination nodes.
an edge always extends from the side/bottom of src node and goes to the
middle of the of dest node
'''
ascii_src = src.box
ascii_dest = dest.box
# for dest, edge will connect in middle of top
dest_x = ascii_dest.position.left + ascii_dest.box_width // 2
dest_y = ascii_dest.position.top
src_lbound = ascii_src.position.left
src_rbound = ascii_src.position.left + ascii_src.box_width
src_ymiddle = ascii_src.position.top + ascii_src.box_height // 2
# based on entry point on dest, determine whether
# the line will go from left, right, or center
# of source
if src_lbound > dest_x:
# edge will go from left of source box
# y-axis of where to extend from source
# draw horizontal leg
for i in range(src_lbound, dest_x-1, -1):
screen[src_ymiddle][i] = charset.xside
# draw left out going portrusion
screen[src_ymiddle][src_lbound] = charset.left_out
# draw elbow of edge
screen[src_ymiddle][dest_x] = charset.top_left
# draw vertical leg
for i in range(src_ymiddle+1, dest_y):
screen[i][dest_x] = charset.yside
elif src_rbound < dest_x:
# edge will go from right of source box
for i in range(src_rbound, dest_x+1):
screen[src_ymiddle][i] = charset.xside
# right protrusion
screen[src_ymiddle][src_rbound] = charset.right_out
# elbow
screen[src_ymiddle][dest_x] = charset.top_right
# then draw vertical leg
for i in range(src_ymiddle+1, dest_y):
screen[i][dest_x] = charset.yside
else:
# edge will go from underneath source box
src_ybottom = ascii_src.box_height + ascii_src.position.top
for i in range(src_ybottom, dest_y):
screen[i][dest_x] = charset.yside
# bottom protrusion - goes on box which is one unit
# higher hence -1
screen[src_ybottom-1][dest_x] = charset.bottom_out
# every dest box will have a top out
screen[dest_y][dest_x] = charset.top_out
def draw(screen: List[List[str]], root: Node, padding: int=PADDING, charset=charsets.Unicode):
'''
traverse tree and draw nodes and edges
'''
stack = [root]
edges = []
while stack:
node = stack.pop()
left_bound, top_bound = node.box.position
box_width, line_width = node.box.box_width, node.box.line_width
draw_node(screen, node.val, left_bound, top_bound, box_width, line_width, padding, charset)
for child in node.children:
# order doesn't matter since each node is drawn independently
stack.append(child)
edges.append((node, child))
# draw edges at the end since draw_edge handles
# adding outgoing protrusion chars on box
for node, child in edges:
draw_edge(screen, node, child, charset)
def draw_line(width, charset=charsets.Unicode)->str:
'''
draw line
'''
return charset.xside*width
================================================
FILE: ascii_tree/external.py
================================================
'''
Utilities for external interactions
'''
from . import charsets
from typing import Callable, Any, List
from .ascii_tree import print_tree
from .custom_types import Node
# params may overlap
SCREEN_PARAM_NAMES = ['screen_width', 'margin', 'padding', 'charset']
BOX_PARAM_NAMES = ['padding', 'box_max_width']
# updated param values
_screen_params = {}
_box_params = {}
def transformed_tree(root: Any, get_val: Callable[[Any], Any], get_children: Callable[[Any], List])->Node:
'''
Utility func
transform tree from arbitrary node type
to tree of `Node`. This is needed since
functions here assume the Node structure.
`get_val` and `get_children` are callables that when called
on source node, return val and children (list) respectively.
'''
troot = Node.init_with_box(get_val(root), **_box_params)
troot.children = [transformed_tree(child, get_val, get_children) for child in get_children(root)]
return troot
def make_and_print_tree(root: Any, get_val: Callable[[Any], Any], get_children: Callable[[Any], List]):
'''
Utility method that transforms and prints tree(s)
'''
print_tree(transformed_tree(root, get_val, get_children), **_screen_params)
def transform_param(param, val):
'''
some param value need to be transformed
'''
if param == 'charset':
if val.lower() == 'unicode':
return charsets.Unicode
return charsets.Ascii
return val
def update_param(param: str, new_val):
'''
updates param dict
'''
param = param.lower()
new_val = transform_param(param, new_val)
if param in SCREEN_PARAM_NAMES:
_screen_params[param] = new_val
print(f'{param} updated to {new_val}')
if param in BOX_PARAM_NAMES:
_box_params[param] = new_val
print(f'{param} updated to {new_val}')
================================================
FILE: ascii_tree/params.py
================================================
SCREEN_WIDTH = 180
MARGIN = 5 # space between nodes
PADDING = 1 # space between node body and node walls
BOX_MAX_WIDTH = 30 # maximum width of ascii box
SHOW_CONT_DIALOG = True
================================================
FILE: examples/big_tree.py
================================================
'''
If the tree can't fit on one screen, it will be split into
pages and printed over multiple pages.
First let's construct a big tree.
'''
from ascii_tree import make_and_print_tree
from node_types import NTreeNode
'''
First generate a really wide tree
'''
root0 = NTreeNode('onomatopaie'*10)
root0.children = [NTreeNode('boobar'), NTreeNode('car'), NTreeNode('scuba'), NTreeNode('tarzanman'), NTreeNode('smartcar')]
root0.children[-1].children = [NTreeNode('Kathmandu'), NTreeNode('smartcat '*20), NTreeNode('lorem ipsum dolor...')]
root0.children[-1].children[0].children = [NTreeNode('fooball '*10)]
root0.children[-1].children[1].children = [NTreeNode('foosbag')]
root0.children.append(NTreeNode('carbar'))
print("Printing root0...")
make_and_print_tree(root0, lambda node: node.val, lambda node:node.children)
================================================
FILE: examples/big_tree1.py
================================================
from ascii_tree import make_and_print_tree
from node_types import NTreeNode
'''
Now make it ever wider, such that it can't fit on the screen.
This will print multiple pages now
'''
root1 = NTreeNode('onomatopaie'*10)
root1.children = [NTreeNode(f'lorem ipsum dolor...{i}') for i in range(10)]
print("Printing root1...")
make_and_print_tree(root1, lambda node: node.val, lambda node:node.children)
'''
Notice how the object is split over multiple pages.
Also observer that the parent is duplicated in both pages.
This is done to make the graph more readable.
The splitting algorithm will split along any level of
the tree.
'''
================================================
FILE: examples/big_tree2.py
================================================
from ascii_tree import make_and_print_tree
from node_types import NTreeNode
root2 = NTreeNode('onomatopaie'*10)
root2.children = [NTreeNode('boobar'), NTreeNode('car'), NTreeNode('scuba'), NTreeNode('tarzanman'), NTreeNode('smartcar')]
root2.children[-1].children = [NTreeNode('Kathmandu'*5), NTreeNode('smartcat '*20), NTreeNode('lorem ipsum dolor...'*20)]
root2.children[-1].children.extend([NTreeNode('Kathmandu'*5), NTreeNode('smartcat '*20), NTreeNode('lorem ipsum dolor...'*20)])
root2.children[-1].children[0].children = [NTreeNode('fooball '*10)]
root2.children[-1].children[1].children = [NTreeNode('foosbag')]
root2.children.append(NTreeNode('carbar'))
print("Printing root2...")
make_and_print_tree(root2, lambda node: node.val, lambda node:node.children)
================================================
FILE: examples/change_config.py
================================================
'''
If the tree can't fit on one screen, it will be split into
pages and printed over multiple pages.
First let's construct a big tree.
'''
from ascii_tree import make_and_print_tree, update_param
class NTreeNode:
def __init__(self, val):
self.val = val
self.children = []
'''
First generate a really wide tree
'''
root0 = NTreeNode('onomatopaie'*10)
root0.children = [NTreeNode('boobar'), NTreeNode('car'), NTreeNode('scuba'), NTreeNode('tarzanman'), NTreeNode('smartcar')]
root0.children[-1].children = [NTreeNode('Kathmandu'), NTreeNode('smartcat '*20), NTreeNode('lorem ipsum dolor...')]
root0.children[-1].children[0].children = [NTreeNode('fooball '*10)]
root0.children[-1].children[1].children = [NTreeNode('foosbag')]
root0.children.append(NTreeNode('carbar'))
update_param('box_max_width', 20)
update_param('screen_width', 100)
print("Printing root0...")
make_and_print_tree(root0, lambda node: node.val, lambda node:node.children)
================================================
FILE: examples/node_types.py
================================================
class BinaryTreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
class NTreeNode:
def __init__(self, val):
self.val = val
self.children = []
================================================
FILE: examples/small_tree.py
================================================
'''
Ascii Tree is library for printing beautiful ascii trees.
The first step is to convert the tree into another
tree that is comrehensible by the library.
For this we'll use the following.
'''
from ascii_tree import transformed_tree, print_tree
from node_types import BinaryTreeNode
'''
construct a dummy tre
'''
root = BinaryTreeNode(1)
root.left = BinaryTreeNode(2)
root.right = BinaryTreeNode(3)
root.left.left = BinaryTreeNode(4)
root.left.right = BinaryTreeNode(5)
root.right.left = BinaryTreeNode(6)
root.right.right = BinaryTreeNode(7)
'''
transformed_tree(root, get_val: Callable, get_children: Callable):
transformed_tree accepts the root node of some arbitrary tree class,
a callable, called with the node as the argument to get its value
and another callable to get the current node's children
'''
def get_val(node):
'''
get value from node
'''
return node.value
def get_children(node):
'''
get list of children from node
'''
children = []
if node.left:
children.append(node.left)
if node.right:
children.append(node.right)
return children
troot = transformed_tree(root, get_val, get_children)
print_tree(troot)
'''
page: 0
------
| |
---- 1 -------------
| | | |
| ------ |
| |
| |
| |
| |
| |
------ ------
| | | |
---- 2 --- ---- 3 ---
| | | | | | | |
| ------ | | ------ |
| | | |
| | | |
| | | |
| | | |
| | | |
------ ------ ------ ------
| | | | | | | |
| 4 | | 5 | | 6 | | 7 |
| | | | | | | |
------ ------ ------ ------
'''
================================================
FILE: examples/small_tree1.py
================================================
from ascii_tree import make_and_print_tree, update_param
from node_types import NTreeNode
root = NTreeNode('Lorem ipsum dolor sit amet')
root.children = [NTreeNode('consectetur adipiscing elit.'), NTreeNode('Etiam laoreet congue')]
root.children[0].children = [NTreeNode('Pellentesque finibus metus eget'), NTreeNode('ante aliquet ullamcorper')]
root.children[0].children[0].children = [NTreeNode('Sed nec nibh cursus'), NTreeNode('volutpat lacus at, euismod mi')]
update_param('screen_width', 120)
update_param('box_max_width', 20)
make_and_print_tree(root, lambda n: n.val, lambda n:n.children)
================================================
FILE: examples/small_tree2.py
================================================
from ascii_tree import make_and_print_tree, update_param
from node_types import NTreeNode
root = NTreeNode('Lorem ipsum dolor sit amet')
root.children = [NTreeNode('consectetur adipiscing elit.'), NTreeNode('Etiam laoreet congue')]
root.children[0].children = [NTreeNode('Pellentesque finibus metus eget'), NTreeNode('ante aliquet ullamcorper')]
root.children[1].children = [NTreeNode('Morbi porta, diam at imperdiet venenatis'), NTreeNode('neque eros bibendum tortor, quis')]
update_param('screen_width', 120)
update_param('box_max_width', 20)
update_param('charset', 'ascii')
make_and_print_tree(root, lambda n: n.val, lambda n:n.children)
================================================
FILE: setup.py
================================================
from setuptools import setup, find_packages
setup(name='ascii_tree',
version='0.1',
description='create beautiful ascii trees',
python_requires='>=3',
packages=find_packages()
)
gitextract_z82j96jg/ ├── LICENSE ├── README.md ├── ascii_tree/ │ ├── __init__.py │ ├── ascii_tree.py │ ├── charsets.py │ ├── custom_types.py │ ├── draw.py │ ├── external.py │ └── params.py ├── examples/ │ ├── big_tree.py │ ├── big_tree1.py │ ├── big_tree2.py │ ├── change_config.py │ ├── node_types.py │ ├── small_tree.py │ ├── small_tree1.py │ └── small_tree2.py └── setup.py
SYMBOL INDEX (41 symbols across 8 files)
FILE: ascii_tree/ascii_tree.py
function get_margin (line 16) | def get_margin(node_count: int, margin: int=MARGIN)->float:
function recomp_node_width (line 23) | def recomp_node_width(root: Node, add_on: Node=None, margin: int=MARGIN)...
function get_node_widths (line 46) | def get_node_widths(root: Node, margin: int=MARGIN):
function get_tree_height (line 73) | def get_tree_height(root: Node, margin: int=MARGIN)->int:
function position_nodes (line 88) | def position_nodes(root: Node, left_offset: int, top_offset: int, margin...
function update_page_nums (line 126) | def update_page_nums(page_map: dict, splits: List[Node]):
function split_tree (line 136) | def split_tree(root: Node, max_width: int=SCREEN_WIDTH, first_max_width:...
function draw_tree (line 218) | def draw_tree(root, screen_width: int=SCREEN_WIDTH, margin: int=MARGIN, ...
function print_screen (line 247) | def print_screen(screen):
function print_tree (line 255) | def print_tree(root: Node, screen_width: int=SCREEN_WIDTH, margin: int=M...
FILE: ascii_tree/charsets.py
class Charset (line 5) | class Charset:
method __init__ (line 6) | def __init__(self, name=None, xside=None, yside=None, top=None, bottom...
method __str__ (line 50) | def __str__(self):
FILE: ascii_tree/custom_types.py
class Node (line 10) | class Node:
method __init__ (line 14) | def __init__(self, val):
method init_with_box (line 21) | def init_with_box(cls, val, **kwargs):
method __repr__ (line 30) | def __repr__(self):
method __str__ (line 33) | def __str__(self):
method is_leaf (line 37) | def is_leaf(self):
method __copy__ (line 40) | def __copy__(self):
class AsciiBox (line 50) | class AsciiBox:
method __init__ (line 55) | def __init__(self, text: str, box_max_width: int=BOX_MAX_WIDTH, paddin...
method __repr__ (line 77) | def __repr__(self):
method width (line 81) | def width(self):
method box_dims (line 84) | def box_dims(self, text: str, box_max_width: int, padding: int):
FILE: ascii_tree/draw.py
function draw_node (line 10) | def draw_node(screen: list, text: str, left_bound: int, top_bound: int, ...
function draw_edge (line 78) | def draw_edge(screen, src: Node, dest: Node, charset=charsets.Ascii):
function draw (line 134) | def draw(screen: List[List[str]], root: Node, padding: int=PADDING, char...
function draw_line (line 155) | def draw_line(width, charset=charsets.Unicode)->str:
FILE: ascii_tree/external.py
function transformed_tree (line 18) | def transformed_tree(root: Any, get_val: Callable[[Any], Any], get_child...
function make_and_print_tree (line 32) | def make_and_print_tree(root: Any, get_val: Callable[[Any], Any], get_ch...
function transform_param (line 39) | def transform_param(param, val):
function update_param (line 51) | def update_param(param: str, new_val):
FILE: examples/change_config.py
class NTreeNode (line 9) | class NTreeNode:
method __init__ (line 10) | def __init__(self, val):
FILE: examples/node_types.py
class BinaryTreeNode (line 2) | class BinaryTreeNode:
method __init__ (line 3) | def __init__(self, value):
class NTreeNode (line 9) | class NTreeNode:
method __init__ (line 10) | def __init__(self, val):
FILE: examples/small_tree.py
function get_val (line 30) | def get_val(node):
function get_children (line 37) | def get_children(node):
Condensed preview — 18 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (46K chars).
[
{
"path": "LICENSE",
"chars": 1074,
"preview": "MIT License\n\nCopyright (c) [2019] [Spandan Bemby]\n\nPermission is hereby granted, free of charge, to any person obtaining"
},
{
"path": "README.md",
"chars": 12317,
"preview": "Ascii Tree\n==========\n\nGenerate beautiful ascii trees.\n```\nI think that I shall never see\nA graph more lovely than a tre"
},
{
"path": "ascii_tree/__init__.py",
"chars": 110,
"preview": "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",
"chars": 9679,
"preview": "'''\nConstruct ascii tree and determine placement of tree\nonto page or possibly onto multiple pages if it's too\nbig to fi"
},
{
"path": "ascii_tree/charsets.py",
"chars": 2280,
"preview": "'''\ndifferent characters to draw the ascii tree with\n'''\n\nclass Charset:\n def __init__(self, name=None, xside=None, y"
},
{
"path": "ascii_tree/custom_types.py",
"chars": 3308,
"preview": "import math\nfrom collections import namedtuple, deque\nfrom copy import copy\nfrom .params import PADDING, BOX_MAX_WIDTH\n\n"
},
{
"path": "ascii_tree/draw.py",
"chars": 5596,
"preview": "'''\nFunctions for drawing\n'''\nfrom . import charsets\nfrom typing import List\nfrom .params import PADDING\nfrom .custom_ty"
},
{
"path": "ascii_tree/external.py",
"chars": 1846,
"preview": "'''\nUtilities for external interactions\n'''\nfrom . import charsets\nfrom typing import Callable, Any, List\nfrom .ascii_tr"
},
{
"path": "ascii_tree/params.py",
"chars": 180,
"preview": "SCREEN_WIDTH = 180\nMARGIN = 5 # space between nodes\nPADDING = 1 # space between node body and node walls\nBOX_MAX_WIDTH"
},
{
"path": "examples/big_tree.py",
"chars": 820,
"preview": "'''\nIf the tree can't fit on one screen, it will be split into\npages and printed over multiple pages.\nFirst let's constr"
},
{
"path": "examples/big_tree1.py",
"chars": 629,
"preview": "from ascii_tree import make_and_print_tree\nfrom node_types import NTreeNode\n\n'''\nNow make it ever wider, such that it c"
},
{
"path": "examples/big_tree2.py",
"chars": 768,
"preview": "from ascii_tree import make_and_print_tree\nfrom node_types import NTreeNode\n\nroot2 = NTreeNode('onomatopaie'*10)\nroot2.c"
},
{
"path": "examples/change_config.py",
"chars": 968,
"preview": "'''\nIf the tree can't fit on one screen, it will be split into\npages and printed over multiple pages.\nFirst let's constr"
},
{
"path": "examples/node_types.py",
"chars": 231,
"preview": "\nclass BinaryTreeNode:\n def __init__(self, value):\n self.value = value\n self.left = None\n self.r"
},
{
"path": "examples/small_tree.py",
"chars": 1996,
"preview": "'''\nAscii Tree is library for printing beautiful ascii trees.\nThe first step is to convert the tree into another\ntree th"
},
{
"path": "examples/small_tree1.py",
"chars": 600,
"preview": "from ascii_tree import make_and_print_tree, update_param\nfrom node_types import NTreeNode\n\nroot = NTreeNode('Lorem ipsum"
},
{
"path": "examples/small_tree2.py",
"chars": 645,
"preview": "from ascii_tree import make_and_print_tree, update_param\nfrom node_types import NTreeNode\n\nroot = NTreeNode('Lorem ipsum"
},
{
"path": "setup.py",
"chars": 204,
"preview": "from setuptools import setup, find_packages\n\n\nsetup(name='ascii_tree',\n version='0.1',\n description='create be"
}
]
About this extraction
This page contains the full source code of the spandanb/ascii_tree GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 18 files (42.2 KB), approximately 9.4k tokens, and a symbol index with 41 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.