-
-
Notifications
You must be signed in to change notification settings - Fork 19
/
astpretty.py
146 lines (121 loc) · 4.27 KB
/
astpretty.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
from __future__ import annotations
import argparse
import ast
import contextlib
from collections.abc import Generator
from collections.abc import Sequence
from typing import Any
AST: tuple[type[Any], ...] = (ast.AST,)
expr_context: tuple[type[Any], ...] = (ast.expr_context,)
def _is_sub_node(node: object) -> bool:
return isinstance(node, AST) and not isinstance(node, expr_context)
def _is_leaf(node: ast.AST) -> bool:
for field in node._fields:
attr = getattr(node, field)
if _is_sub_node(attr):
return False
elif isinstance(attr, (list, tuple)):
for val in attr:
if _is_sub_node(val):
return False
else:
return True
def _fields(n: ast.AST, show_offsets: bool = True) -> tuple[str, ...]:
if show_offsets:
return n._attributes + n._fields
else:
return n._fields
def _leaf(node: ast.AST, show_offsets: bool = True) -> str:
if isinstance(node, AST):
return '{}({})'.format(
type(node).__name__,
', '.join(
'{}={}'.format(
field,
_leaf(getattr(node, field), show_offsets=show_offsets),
)
for field in _fields(node, show_offsets=show_offsets)
),
)
elif isinstance(node, list):
return '[{}]'.format(
', '.join(_leaf(x, show_offsets=show_offsets) for x in node),
)
else:
return repr(node)
def pformat(
node: ast.AST | None | str,
indent: str | int = ' ',
show_offsets: bool = True,
_indent: int = 0,
) -> str:
if node is None:
return repr(node)
elif isinstance(node, str): # pragma: no cover (ast27 typed-ast args)
return repr(node)
elif _is_leaf(node):
return _leaf(node, show_offsets=show_offsets)
else:
if isinstance(indent, int):
indent_s = indent * ' '
else:
indent_s = indent
class state:
indent = _indent
@contextlib.contextmanager
def indented() -> Generator[None]:
state.indent += 1
yield
state.indent -= 1
def indentstr() -> str:
return state.indent * indent_s
def _pformat(el: ast.AST | None | str, _indent: int = 0) -> str:
return pformat(
el, indent=indent, show_offsets=show_offsets,
_indent=_indent,
)
out = type(node).__name__ + '(\n'
with indented():
for field in _fields(node, show_offsets=show_offsets):
attr = getattr(node, field)
if attr == []:
representation = '[]'
elif (
isinstance(attr, list) and
len(attr) == 1 and
isinstance(attr[0], AST) and
_is_leaf(attr[0])
):
representation = f'[{_pformat(attr[0])}]'
elif isinstance(attr, list):
representation = '[\n'
with indented():
for el in attr:
representation += '{}{},\n'.format(
indentstr(), _pformat(el, state.indent),
)
representation += indentstr() + ']'
elif isinstance(attr, AST):
representation = _pformat(attr, state.indent)
else:
representation = repr(attr)
out += f'{indentstr()}{field}={representation},\n'
out += indentstr() + ')'
return out
def pprint(*args: Any, **kwargs: Any) -> None:
print(pformat(*args, **kwargs))
def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filename')
parser.add_argument(
'--no-show-offsets', dest='show_offsets',
action='store_false',
)
args = parser.parse_args(argv)
with open(args.filename, 'rb') as f:
contents = f.read()
tree = ast.parse(contents, filename=args.filename, type_comments=True)
pprint(tree, show_offsets=args.show_offsets)
return 0
if __name__ == '__main__':
raise SystemExit(main())