-
-
Notifications
You must be signed in to change notification settings - Fork 325
/
Copy pathupdate_vdom_usages.py
92 lines (74 loc) · 3.05 KB
/
update_vdom_usages.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
import ast
import re
import sys
from keyword import kwlist
from pathlib import Path
from textwrap import indent
from typing import Iterator
from idom import html
_CAMEL_CASE_SUB_PATTERN = re.compile(r"(?<!^)(?=[A-Z])")
def update_vdom_constructor_usages(file: Path) -> None:
tree = ast.parse(file.read_text())
changed: list[tuple[tuple[ast.AST, ...], ast.AST]] = []
for parents, node in walk_with_parent(tree):
if isinstance(node, ast.Call):
func = node.func
match func:
case ast.Attribute():
name = func.attr
case ast.Name(ctx=ast.Load()):
name = func.id
case _:
name = ""
if hasattr(html, name):
match node.args:
case [ast.Dict(keys, values), *_]:
new_keywords = list(node.keywords)
for k, v in zip(keys, values):
if isinstance(k, ast.Constant) and isinstance(k.value, str):
new_keywords.append(
ast.keyword(arg=conv_attr_name(k.value), value=v)
)
else:
break
else:
node.args = node.args[1:]
node.keywords = new_keywords
changed.append((parents, node))
case _:
pass
if not changed:
return
tree = ast.fix_missing_locations(tree)
lines = file.read_text().split("\n")
nodes_to_unparse: set[ast.AST] = set()
for parents, node in reversed(changed):
for i in range(len(parents) - 2):
this_parent, next_parent = parents[i : i + 2]
if isinstance(next_parent, ast.stmt):
if isinstance(next_parent, (ast.FunctionDef, ast.ClassDef)):
nodes_to_unparse.add(this_parent)
else:
nodes_to_unparse.add(next_parent)
break
for node in sorted(nodes_to_unparse, key=lambda n: n.lineno, reverse=True):
replacement = indent(ast.unparse(node), " " * node.col_offset)
if node.end_lineno:
lines[node.lineno - 1 : node.end_lineno] = [replacement]
else:
lines[node.lineno - 1] = replacement
file.write_text("\n".join(lines))
def walk_with_parent(
node: ast.AST, parents: tuple[ast.AST, ...] = ()
) -> Iterator[tuple[tuple[ast.AST, ...], ast.AST]]:
parents = (node,) + parents
for child in ast.iter_child_nodes(node):
yield parents, child
yield from walk_with_parent(child, parents)
def conv_attr_name(name: str) -> str:
new_name = _CAMEL_CASE_SUB_PATTERN.sub("_", name).replace("-", "_").lower()
return f"{new_name}_" if new_name in kwlist else new_name
if __name__ == "__main__":
for pattern in sys.argv[1:]:
for file in Path.cwd().glob(pattern):
update_vdom_constructor_usages(file)