Skip to content

Commit aaacea1

Browse files
committed
add key rewrite script
1 parent 37a4a89 commit aaacea1

File tree

3 files changed

+161
-147
lines changed

3 files changed

+161
-147
lines changed

src/idom/__main__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import click
22

33
import idom
4-
from idom._console.update_html_usages import update_html_usages
4+
from idom._console.rewrite_key_declarations import rewrite_key_declarations
55

66

77
@click.group()
@@ -10,7 +10,7 @@ def app() -> None:
1010
pass
1111

1212

13-
app.add_command(update_html_usages)
13+
app.add_command(rewrite_key_declarations)
1414

1515

1616
if __name__ == "__main__":

src/idom/_console/update_html_usages.py renamed to src/idom/_console/rewrite_key_declarations.py

+80-59
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import sys
66
from collections.abc import Sequence
77
from dataclasses import dataclass
8-
from keyword import kwlist
98
from pathlib import Path
109
from textwrap import indent
1110
from tokenize import COMMENT as COMMENT_TOKEN
@@ -22,7 +21,7 @@
2221

2322
@click.command()
2423
@click.argument("paths", nargs=-1, type=click.Path(exists=True))
25-
def update_html_usages(paths: list[str]) -> None:
24+
def rewrite_key_declarations(paths: list[str]) -> None:
2625
"""Rewrite files under the given paths using the new html element API.
2726
2827
The old API required users to pass a dictionary of attributes to html element
@@ -64,9 +63,21 @@ def update_html_usages(paths: list[str]) -> None:
6463
def generate_rewrite(file: Path, source: str) -> str | None:
6564
tree = ast.parse(source)
6665

66+
changed = find_nodes_to_change(tree)
67+
if not changed:
68+
log_could_not_rewrite(file, tree)
69+
return None
70+
71+
new = rewrite_changed_nodes(file, source, tree, changed)
72+
log_could_not_rewrite(file, ast.parse(new))
73+
74+
return new
75+
76+
77+
def find_nodes_to_change(tree: ast.AST) -> list[Sequence[ast.AST]]:
6778
changed: list[Sequence[ast.AST]] = []
6879
for parents, node in walk_with_parent(tree):
69-
if not isinstance(node, ast.Call):
80+
if not (isinstance(node, ast.Call) and node.keywords):
7081
continue
7182

7283
func = node.func
@@ -77,34 +88,62 @@ def generate_rewrite(file: Path, source: str) -> str | None:
7788
else:
7889
continue
7990

91+
for kw in list(node.keywords):
92+
if kw.arg == "key":
93+
break
94+
else:
95+
continue
96+
97+
maybe_attr_dict_node = None
8098
if name == "vdom":
81-
if len(node.args) < 2:
82-
continue
83-
maybe_attr_dict_node = node.args[1]
84-
# remove attr dict from new args
85-
new_args = node.args[:1] + node.args[2:]
99+
if len(node.args) == 1:
100+
# vdom("tag") need to add attr dict
101+
maybe_attr_dict_node = ast.Dict(keys=[], values=[])
102+
node.args.append(maybe_attr_dict_node)
103+
elif isinstance(node.args[1], (ast.Constant, ast.JoinedStr)):
104+
maybe_attr_dict_node = ast.Dict(keys=[], values=[])
105+
node.args.insert(1, maybe_attr_dict_node)
106+
elif len(node.args) >= 2:
107+
maybe_attr_dict_node = node.args[1]
86108
elif hasattr(html, name):
87109
if len(node.args) == 0:
88-
continue
89-
maybe_attr_dict_node = node.args[0]
90-
# remove attr dict from new args
91-
new_args = node.args[1:]
110+
# vdom("tag") need to add attr dict
111+
maybe_attr_dict_node = ast.Dict(keys=[], values=[])
112+
node.args.append(maybe_attr_dict_node)
113+
elif isinstance(node.args[0], (ast.Constant, ast.JoinedStr)):
114+
maybe_attr_dict_node = ast.Dict(keys=[], values=[])
115+
node.args.insert(0, maybe_attr_dict_node)
116+
else:
117+
maybe_attr_dict_node = node.args[0]
118+
119+
if not maybe_attr_dict_node:
120+
continue
121+
122+
if isinstance(maybe_attr_dict_node, ast.Dict):
123+
maybe_attr_dict_node.keys.append(ast.Constant("key"))
124+
maybe_attr_dict_node.values.append(kw.value)
125+
elif (
126+
isinstance(maybe_attr_dict_node, ast.Call)
127+
and isinstance(maybe_attr_dict_node.func, ast.Name)
128+
and maybe_attr_dict_node.func.id == "dict"
129+
and isinstance(maybe_attr_dict_node.func.ctx, ast.Load)
130+
):
131+
maybe_attr_dict_node.keywords.append(ast.keyword(arg="key", value=kw.value))
92132
else:
93133
continue
94134

95-
new_keyword_info = extract_keywords(maybe_attr_dict_node)
96-
if new_keyword_info is not None:
97-
if new_keyword_info.replace:
98-
node.keywords = new_keyword_info.keywords
99-
else:
100-
node.keywords.extend(new_keyword_info.keywords)
135+
node.keywords.remove(kw)
136+
changed.append((node, *parents))
101137

102-
node.args = new_args
103-
changed.append((node, *parents))
138+
return changed
104139

105-
if not changed:
106-
return None
107140

141+
def rewrite_changed_nodes(
142+
file: str,
143+
source: str,
144+
tree: ast.AST,
145+
changed: list[Sequence[ast.AST]],
146+
) -> str:
108147
ast.fix_missing_locations(tree)
109148

110149
lines = source.split("\n")
@@ -171,38 +210,25 @@ def generate_rewrite(file: Path, source: str) -> str | None:
171210
return "\n".join(lines)
172211

173212

174-
def extract_keywords(node: ast.AST) -> KeywordInfo | None:
175-
if isinstance(node, ast.Dict):
176-
keywords: list[ast.keyword] = []
177-
for k, v in zip(node.keys, node.values):
178-
if isinstance(k, ast.Constant) and isinstance(k.value, str):
179-
if k.value == "tagName":
180-
# this is a vdom dict declaration
181-
return None
182-
keywords.append(ast.keyword(arg=conv_attr_name(k.value), value=v))
183-
else:
184-
return KeywordInfo(
185-
replace=True,
186-
keywords=[ast.keyword(arg=None, value=node)],
187-
)
188-
return KeywordInfo(replace=False, keywords=keywords)
189-
elif (
190-
isinstance(node, ast.Call)
191-
and isinstance(node.func, ast.Name)
192-
and node.func.id == "dict"
193-
and isinstance(node.func.ctx, ast.Load)
194-
):
195-
keywords = [ast.keyword(arg=None, value=a) for a in node.args]
196-
for kw in node.keywords:
197-
if kw.arg == "tagName":
198-
# this is a vdom dict declaration
199-
return None
200-
if kw.arg is not None:
201-
keywords.append(ast.keyword(arg=conv_attr_name(kw.arg), value=kw.value))
202-
else:
203-
keywords.append(kw)
204-
return KeywordInfo(replace=False, keywords=keywords)
205-
return None
213+
def log_could_not_rewrite(file: str, tree: ast.AST) -> None:
214+
for node in ast.walk(tree):
215+
if not (isinstance(node, ast.Call) and node.keywords):
216+
continue
217+
218+
func = node.func
219+
if isinstance(func, ast.Attribute):
220+
name = func.attr
221+
elif isinstance(func, ast.Name):
222+
name = func.id
223+
else:
224+
continue
225+
226+
if (
227+
name == "vdom"
228+
or hasattr(html, name)
229+
and any(kw.arg == "key" for kw in node.keywords)
230+
):
231+
click.echo(f"Unable to rewrite usage at {file}:{node.lineno}")
206232

207233

208234
def find_comments(lines: list[str]) -> list[str]:
@@ -223,11 +249,6 @@ def walk_with_parent(
223249
yield from walk_with_parent(child, parents)
224250

225251

226-
def conv_attr_name(name: str) -> str:
227-
new_name = CAMEL_CASE_SUB_PATTERN.sub("_", name).replace("-", "_").lower()
228-
return f"{new_name}_" if new_name in kwlist else new_name
229-
230-
231252
@dataclass
232253
class KeywordInfo:
233254
replace: bool

0 commit comments

Comments
 (0)