Skip to content

Commit 0e8f816

Browse files
committed
code migration
1 parent 085e776 commit 0e8f816

13 files changed

+937
-4
lines changed

README.md

+230-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,230 @@
1-
# pdm-nox-dev-template
2-
Template for python projects managed with pdm and tested with nox
1+
# ascii-tree
2+
3+
(Yet another) ascii tree rendering utility!
4+
5+
There are a bunch existing solutions out there for drawing directory structures in ascii, but not many with the ability to draw non-filesystem object hierarchies.
6+
7+
`ascii-tree` provides an easy-to-use interface to display any tree-like data as a string.
8+
9+
## Drawing a Directory Tree
10+
One batteries-included feature of `ascii-tree` is the ability to draw directory trees.
11+
12+
This also provides a good example of how you might customize rendering for custom tree data.
13+
14+
```python
15+
import ascii_tree
16+
tree = ascii_tree.renderable_dir_tree(
17+
"./tests/fixtures"
18+
)
19+
print(ascii_tree.render(tree))
20+
```
21+
Output:
22+
```
23+
fixtures /
24+
└─ root_dir /
25+
├─ child_dir_one /
26+
│ ├─ grandchild_dir_one /
27+
│ │ └─ great_grandchild_file_one.txt
28+
│ └─ grandchild_file_one.txt
29+
└─ child_dir_two /
30+
├─ grandchild_dir_two /
31+
│ └─ great_grandchild_file_two.txt
32+
└─ grandchild_file_two.txt
33+
```
34+
By default, this will recurse until all directories and files are visited.
35+
### Setting Traversal Depth
36+
By providing a `max_depth` argument, you can only search directories up to a certain level of nesting:
37+
```python
38+
import ascii_tree
39+
tree = ascii_tree.renderable_dir_tree(
40+
"./tests/fixtures",
41+
max_depth=2
42+
)
43+
print(ascii_tree.render(tree))
44+
```
45+
Output:
46+
```
47+
fixtures /
48+
└─ root_dir /
49+
├─ child_dir_one / ...
50+
└─ child_dir_two / ...
51+
```
52+
The slashes after directories and the ellipses (`...`) after max depth can be controlled with parameters on `renderable_dir_tree`.
53+
```python
54+
import ascii_tree
55+
tree = ascii_tree.renderable_dir_tree(
56+
"./tests/fixtures",
57+
max_depth=2,
58+
slash_after_dir=False,
59+
ellipsis_after_max_depth=False
60+
)
61+
```
62+
Output:
63+
```
64+
fixtures
65+
└─ root_dir
66+
├─ child_dir_one
67+
└─ child_dir_two
68+
```
69+
### Dealing with Permission Errors
70+
Finally, if you are traversing files with mixed permission levels, it can be useful to skip files that would otherwise cause a `PermissionError` when access isn't allowed. Use the
71+
`skip_if_no_permission` parameter:
72+
```python
73+
import ascii_tree
74+
tree = ascii_tree.renderable_dir_tree(
75+
"here/be/dragons",
76+
skip_if_no_permission=True
77+
)
78+
print(ascii_tree.render(tree))
79+
```
80+
Output:
81+
```
82+
dragons /
83+
├─ pendor /
84+
│ └─ yevaud.txt
85+
├─ lonely_mountain [Permission Denied]
86+
└─ westeros /
87+
└─ balereon.txt
88+
```
89+
## Building a Tree from Scratch
90+
91+
Trees can be built up from a chain of `TextRenderNode` instances. `TextRenderNode` is extremely simple; just a `display` and a `children` attribute.
92+
93+
```python
94+
import ascii_tree
95+
96+
grandchild = ascii_tree.TextRenderNode("grandchild")
97+
grandchild_two = ascii_tree.TextRenderNode("grandchild_two")
98+
child = ascii_tree.TextRenderNode("child")
99+
root = ascii_tree.TextRenderNode("root")
100+
101+
print(ascii_tree.render(root))
102+
```
103+
104+
Output:
105+
```
106+
root
107+
└─ child
108+
├─ grandchild
109+
└─ grandchild_two
110+
```
111+
112+
## Making a Custom ASCII-Tree Interface
113+
114+
Unless you're very lucky, your data *probably* doesn't have `display` and `children` attributes. So we'll need to use an interface!
115+
116+
Let's assume you've got your business data in a class like so:
117+
```python
118+
class YourClass:
119+
def __init__(self, name, child_names=None):
120+
self.name = name
121+
self.child_names = child_names or []
122+
123+
def get_children(self):
124+
return [YourClass(name) for name in self.child_names]
125+
```
126+
We'll want to map the `name` attribute to `display`, and the `get_children` method to `children`.
127+
```python
128+
import ascii_tree
129+
130+
obj = YourClass(name="root", children=["one", "two"])
131+
132+
node = ascii_tree.renderable(
133+
obj, display_attr="name", children_method=obj.get_children
134+
)
135+
136+
print(ascii_tree.render(node))
137+
```
138+
Output:
139+
```
140+
root
141+
├─ one
142+
└─ two
143+
```
144+
145+
The `renderable` function allows you to specify an attribute or a method for both `display` and `children`
146+
147+
## Parent-Only Interface
148+
149+
Oftentimes, hierarchically-organized objects only contain references to their parents instead of their children. In that case, we need to do a little more transformation in order to build the renderable tree.
150+
151+
```python
152+
class MyObjectType:
153+
def __init__(self, name, parent):
154+
self.name = name
155+
self.parent = parent
156+
157+
root = MyObjectType("root", parent=None)
158+
child = MyObjectType("child_one", parent=root)
159+
grandchild = MyObjectType("grandchild_one", parent=child)
160+
```
161+
In this case, we need to build the tree *upwards* from the leaf nodes (the grandchildren).
162+
To do so, pass the leaf-level objects in the tree to the `renderable_from_parents` function:
163+
164+
```python
165+
import ascii_tree
166+
tree = ascii_tree.renderable_from_parents(
167+
[grandchild],
168+
parent_attr="parent"
169+
)
170+
print(ascii_tree.render(tree[0]))
171+
```
172+
Root-level nodes in your hierarchy *must* return `None` from either `parent_attr` or `parent_method`.
173+
174+
Note that `renderable_from_parents` always **returns a list** -- this is to ensure that
175+
data with multiple root nodes are supported.
176+
177+
Output:
178+
```
179+
root
180+
└─ child
181+
└─ grandchild
182+
```
183+
## Rendering Styles
184+
185+
`ascii-tree` currently provides three styles of rendering:
186+
### Solid Line Style
187+
```python
188+
render(tree, style=styles.solid_line_style)
189+
```
190+
```
191+
root
192+
├─ child_one
193+
│ ├─ grandchild_one
194+
│ │ ├─ great_grandchild_one
195+
│ │ └─ great_grandchild_two
196+
│ └─ grandchild_two
197+
│ ├─ great_grandchild_three
198+
│ └─ great_grandchild_four
199+
└─ child_two
200+
```
201+
### Basic Style
202+
```python
203+
render(tree, style=styles.basic_style)
204+
```
205+
```
206+
root
207+
+- child_one
208+
| +- grandchild_one
209+
| | +- great_grandchild_one
210+
| | +- great_grandchild_two
211+
| +- grandchild_two
212+
| +- great_grandchild_three
213+
| +- great_grandchild_four
214+
+- child_two
215+
```
216+
### Clean Style
217+
```python
218+
render(tree, style=styles.clean_style)
219+
```
220+
```
221+
root
222+
· child_one
223+
· grandchild_one
224+
· great_grandchild_one
225+
· great_grandchild_two
226+
· grandchild_two
227+
· great_grandchild_three
228+
· great_grandchild_four
229+
· child_two
230+
```

noxfile.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
@nox.session
55
def tests(session):
66
session.install(".")
7-
session.run("python", "-m", "unittest", "discover", "-s", "tests")
7+
session.run("pytest", "tests")

pdm.lock

+64-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ dev = [
44
"nox>=2022.11.21",
55
"pre-commit>=2.21.0",
66
"black>=23.3.0",
7+
"pytest>=7.2.2",
78
]
89

910
[project]

0 commit comments

Comments
 (0)