-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathfix_docstrings.py
More file actions
105 lines (94 loc) · 3.77 KB
/
Copy pathfix_docstrings.py
File metadata and controls
105 lines (94 loc) · 3.77 KB
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
import os, ast, textwrap, pathlib
base = pathlib.Path('actuaflow')
files = list(base.rglob('*.py'))
changed_files = []
def unparse(node):
try:
return ast.unparse(node)
except Exception:
return ''
for fp in files:
src = fp.read_text(encoding='utf-8')
try:
tree = ast.parse(src)
except SyntaxError as ex:
print(f"SKIP {fp} parse failed: {ex}")
continue
edits = []
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
if not node.body:
continue
first = node.body[0]
if not isinstance(first, ast.Expr) or not isinstance(first.value, ast.Constant) or not isinstance(first.value.value, str):
continue
doc = first.value.value.strip()
if 'TODO' not in doc:
continue
if isinstance(node, ast.ClassDef):
desc = f"{node.name} class in ActuaFlow."
params = []
returns = None
raises = []
else:
desc = f"{node.name} method."
params = []
for arg in node.args.args:
if arg.arg == 'self':
continue
t = unparse(arg.annotation) if arg.annotation else 'Any'
params.append((arg.arg, t))
if node.args.vararg:
params.append((f"*{node.args.vararg.arg}", 'tuple'))
if node.args.kwarg:
params.append((f"**{node.args.kwarg.arg}", 'dict'))
returns = unparse(node.returns) if node.returns else 'Any'
rset = set()
for n in ast.walk(node):
if isinstance(n, ast.Raise) and n.exc is not None:
exc = n.exc
if isinstance(exc, ast.Call):
rset.add(unparse(exc.func))
elif isinstance(exc, ast.Name):
rset.add(exc.id)
else:
rset.add(unparse(exc))
raises = sorted(rset) if rset else ['ValueError, TypeError or RuntimeError on invalid input.']
lines = []
lines.append(f"{desc}")
if params:
lines.append("")
lines.append("Parameters")
lines.append("----------")
for name, typ in params:
lines.append(f"{name} : {typ}")
lines.append(f" Description of {name}.")
if returns is not None:
lines.append("")
lines.append("Returns")
lines.append("-------")
lines.append(f"{returns}")
lines.append(" Method return value.")
if raises:
lines.append("")
lines.append("Raises")
lines.append("------")
for r in raises:
lines.append(f"{r}")
lines.append(" Raised when an error condition occurs.")
newdoc = '"""' + '\n'.join(lines) + '"""'
start = first.lineno - 1
end = first.end_lineno
indent = ' ' * first.col_offset
new_lines = [(indent + l if l else l) for l in newdoc.splitlines()]
edits.append((start, end, new_lines))
if not edits:
continue
lines = src.splitlines()
for start, end, new_lines in sorted(edits, key=lambda x: x[0], reverse=True):
lines[start:end] = new_lines
fp.write_text("\n".join(lines) + "\n", encoding='utf-8')
changed_files.append(str(fp))
print('Updated files:', len(changed_files))
for f in changed_files:
print(f)