Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f5e3cb8

Browse filesBrowse files
committed
minor refactorings
1 parent 07083b8 commit f5e3cb8
Copy full SHA for f5e3cb8

File tree

Expand file treeCollapse file tree

3 files changed

+279
-7
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+279
-7
lines changed
+75Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/usr/bin/env python3
2+
3+
from importlib import import_module
4+
import sys
5+
6+
7+
SP = '\N{SPACE}'
8+
HLIN = '\N{BOX DRAWINGS LIGHT HORIZONTAL}' * 2 + SP # ──
9+
VLIN = '\N{BOX DRAWINGS LIGHT VERTICAL}' + SP * 3 # │
10+
TEE = '\N{BOX DRAWINGS LIGHT VERTICAL AND RIGHT}' + HLIN # ├──
11+
ELBOW = '\N{BOX DRAWINGS LIGHT UP AND RIGHT}' + HLIN # └──
12+
13+
14+
def subclasses(cls):
15+
try:
16+
return cls.__subclasses__()
17+
except TypeError: # handle the `type` type
18+
return cls.__subclasses__(cls)
19+
20+
21+
def tree(cls, level=0, last_sibling=True):
22+
yield cls, level, last_sibling
23+
chidren = subclasses(cls)
24+
if chidren:
25+
last = chidren[-1]
26+
for child in chidren:
27+
yield from tree(child, level + 1, child is last)
28+
29+
30+
def render_lines(tree_generator):
31+
cls, _, _ = next(tree_generator)
32+
yield cls.__name__
33+
prefix = ''
34+
for cls, level, last in tree_generator:
35+
prefix = prefix[: 4 * (level - 1)]
36+
prefix = prefix.replace(TEE, VLIN).replace(ELBOW, SP * 4)
37+
prefix += ELBOW if last else TEE
38+
yield prefix + cls.__name__
39+
40+
41+
def draw(cls):
42+
for line in render_lines(tree(cls)):
43+
print(line)
44+
45+
46+
def parse(name):
47+
if '.' in name:
48+
return name.rsplit('.', 1)
49+
else:
50+
return 'builtins', name
51+
52+
53+
def main(name):
54+
module_name, cls_name = parse(name)
55+
try:
56+
cls = getattr(import_module(module_name), cls_name)
57+
except ModuleNotFoundError:
58+
print(f'*** Could not import {module_name!r}.')
59+
except AttributeError:
60+
print(f'*** {cls_name!r} not found in {module_name!r}.')
61+
else:
62+
if isinstance(cls, type):
63+
draw(cls)
64+
else:
65+
print(f'*** {cls_name!r} is not a class.')
66+
67+
68+
if __name__ == '__main__':
69+
if len(sys.argv) == 2:
70+
main(sys.argv[1])
71+
else:
72+
print('Usage:'
73+
f'\t{sys.argv[0]} Class # for builtin classes\n'
74+
f'\t{sys.argv[0]} package.Class # for other classes'
75+
)
+181Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
from textwrap import dedent
2+
from typing import SupportsBytes
3+
from classtree import tree, render_lines, main, subclasses
4+
5+
from abc import ABCMeta
6+
7+
8+
def test_subclasses():
9+
result = subclasses(UnicodeError)
10+
assert set(result) >= {UnicodeEncodeError, UnicodeDecodeError}
11+
12+
13+
def test_subclasses_of_type():
14+
"""
15+
The `type` class is a special case because `type.__subclasses__()`
16+
is an unbound method when called on it, so we must call it as
17+
`type.__subclasses__(type)` just for `type`.
18+
19+
This test does not verify the full list of results, but just
20+
checks that `abc.ABCMeta` is included, because that's the only
21+
subclass of `type` (i.e. metaclass) I we get when I run
22+
`$ classtree.py type` at the command line.
23+
24+
However, the Python console and `pytest` both load other modules,
25+
so `subclasses` may find more subclasses of `type`—for example,
26+
`enum.EnumMeta`.
27+
"""
28+
result = subclasses(type)
29+
assert ABCMeta in result
30+
31+
32+
def test_tree_1_level():
33+
result = list(tree(TabError))
34+
assert result == [(TabError, 0, True)]
35+
36+
37+
def test_tree_2_levels():
38+
result = list(tree(IndentationError))
39+
assert result == [
40+
(IndentationError, 0, True),
41+
(TabError, 1, True),
42+
]
43+
44+
45+
def test_render_lines_1_level():
46+
result = list(render_lines(tree(TabError)))
47+
assert result == ['TabError']
48+
49+
50+
def test_render_lines_2_levels_1_leaf():
51+
result = list(render_lines(tree(IndentationError)))
52+
expected = [
53+
'IndentationError',
54+
'└── TabError',
55+
]
56+
assert expected == result
57+
58+
59+
def test_render_lines_3_levels_1_leaf():
60+
class X: pass
61+
class Y(X): pass
62+
class Z(Y): pass
63+
result = list(render_lines(tree(X)))
64+
expected = [
65+
'X',
66+
'└── Y',
67+
' └── Z',
68+
]
69+
assert expected == result
70+
71+
72+
def test_render_lines_4_levels_1_leaf():
73+
class Level0: pass
74+
class Level1(Level0): pass
75+
class Level2(Level1): pass
76+
class Level3(Level2): pass
77+
78+
result = list(render_lines(tree(Level0)))
79+
expected = [
80+
'Level0',
81+
'└── Level1',
82+
' └── Level2',
83+
' └── Level3',
84+
]
85+
assert expected == result
86+
87+
88+
def test_render_lines_2_levels_2_leaves():
89+
class Branch: pass
90+
class Leaf1(Branch): pass
91+
class Leaf2(Branch): pass
92+
result = list(render_lines(tree(Branch)))
93+
expected = [
94+
'Branch',
95+
'├── Leaf1',
96+
'└── Leaf2',
97+
]
98+
assert expected == result
99+
100+
101+
def test_render_lines_3_levels_2_leaves_dedent():
102+
class A: pass
103+
class B(A): pass
104+
class C(B): pass
105+
class D(A): pass
106+
class E(D): pass
107+
108+
result = list(render_lines(tree(A)))
109+
expected = [
110+
'A',
111+
'├── B',
112+
'│ └── C',
113+
'└── D',
114+
' └── E',
115+
]
116+
assert expected == result
117+
118+
119+
def test_render_lines_4_levels_4_leaves_dedent():
120+
class A: pass
121+
class B1(A): pass
122+
class C1(B1): pass
123+
class D1(C1): pass
124+
class D2(C1): pass
125+
class C2(B1): pass
126+
class B2(A): pass
127+
expected = [
128+
'A',
129+
'├── B1',
130+
'│ ├── C1',
131+
'│ │ ├── D1',
132+
'│ │ └── D2',
133+
'│ └── C2',
134+
'└── B2',
135+
]
136+
137+
result = list(render_lines(tree(A)))
138+
assert expected == result
139+
140+
141+
def test_main_simple(capsys):
142+
main('IndentationError')
143+
expected = dedent("""
144+
IndentationError
145+
└── TabError
146+
""").lstrip()
147+
captured = capsys.readouterr()
148+
assert captured.out == expected
149+
150+
151+
def test_main_dotted(capsys):
152+
main('collections.abc.Sequence')
153+
expected = dedent("""
154+
Sequence
155+
├── ByteString
156+
├── MutableSequence
157+
│ └── UserList
158+
""").lstrip()
159+
captured = capsys.readouterr()
160+
assert captured.out.startswith(expected)
161+
162+
163+
def test_main_class_not_found(capsys):
164+
main('NoSuchClass')
165+
expected = "*** 'NoSuchClass' not found in 'builtins'.\n"
166+
captured = capsys.readouterr()
167+
assert captured.out == expected
168+
169+
170+
def test_main_module_not_found(capsys):
171+
main('nosuch.module')
172+
expected = "*** Could not import 'nosuch'.\n"
173+
captured = capsys.readouterr()
174+
assert captured.out == expected
175+
176+
177+
def test_main_not_a_class(capsys):
178+
main('collections.abc')
179+
expected = "*** 'abc' is not a class.\n"
180+
captured = capsys.readouterr()
181+
assert captured.out == expected

‎17-it-generator/tree/extra/drawtree.py

Copy file name to clipboardExpand all lines: 17-it-generator/tree/extra/drawtree.py
+23-7Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
11
from tree import tree
22

3-
SP = '\N{SPACE}'
4-
HLIN = '\N{BOX DRAWINGS LIGHT HORIZONTAL}' # ─
5-
ELBOW = f'\N{BOX DRAWINGS LIGHT UP AND RIGHT}{HLIN*2}{SP}' # └──
6-
TEE = f'\N{BOX DRAWINGS LIGHT VERTICAL AND RIGHT}{HLIN*2}{SP}' # ├──
7-
PIPE = f'\N{BOX DRAWINGS LIGHT VERTICAL}{SP*3}' # │
3+
SP = '\N{SPACE}'
4+
HLIN = '\N{BOX DRAWINGS LIGHT HORIZONTAL}' * 2 + SP # ──
5+
VLIN = '\N{BOX DRAWINGS LIGHT VERTICAL}' + SP * 3 # │
6+
TEE = '\N{BOX DRAWINGS LIGHT VERTICAL AND RIGHT}' + HLIN # ├──
7+
ELBOW = '\N{BOX DRAWINGS LIGHT UP AND RIGHT}' + HLIN # └──
8+
9+
10+
def subclasses(cls):
11+
try:
12+
return cls.__subclasses__()
13+
except TypeError: # handle the `type` type
14+
return cls.__subclasses__(cls)
15+
16+
17+
def tree(cls, level=0, last_sibling=True):
18+
yield cls, level, last_sibling
19+
children = subclasses(cls)
20+
if children:
21+
last = children[-1]
22+
for child in children:
23+
yield from tree(child, level+1, child is last)
824

925

1026
def render_lines(tree_iter):
@@ -13,8 +29,8 @@ def render_lines(tree_iter):
1329
prefix = ''
1430

1531
for cls, level, last in tree_iter:
16-
prefix = prefix[:4 * (level-1)]
17-
prefix = prefix.replace(TEE, PIPE).replace(ELBOW, SP*4)
32+
prefix = prefix[:4 * (level - 1)]
33+
prefix = prefix.replace(TEE, VLIN).replace(ELBOW, SP * 4)
1834
prefix += ELBOW if last else TEE
1935
yield prefix + cls.__name__
2036

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.