Skip to content

Commit 9b961e6

Browse files
committed
show_todo
1 parent eddfb12 commit 9b961e6

File tree

1 file changed

+221
-0
lines changed

1 file changed

+221
-0
lines changed

scripts/update_lib/show_todo.py

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
#!/usr/bin/env python
2+
"""
3+
Show prioritized list of modules to update.
4+
5+
Usage:
6+
python scripts/update_lib todo
7+
python scripts/update_lib todo --limit 20
8+
"""
9+
10+
import argparse
11+
import pathlib
12+
import sys
13+
14+
sys.path.insert(0, str(pathlib.Path(__file__).parent.parent))
15+
16+
17+
def compute_todo_list(
18+
cpython_prefix: str = "cpython",
19+
lib_prefix: str = "Lib",
20+
include_done: bool = False,
21+
) -> list[dict]:
22+
"""Compute prioritized list of modules to update.
23+
24+
Scoring:
25+
- Modules with no pylib dependencies: score = -1
26+
- Modules with pylib dependencies: score = count of NOT up-to-date deps
27+
28+
Sorting (ascending by score):
29+
1. More reverse dependencies (modules depending on this) = higher priority
30+
2. Fewer native dependencies = higher priority
31+
32+
Returns:
33+
List of dicts with module info, sorted by priority
34+
"""
35+
from update_lib.deps import get_rust_deps, get_soft_deps, is_up_to_date
36+
from update_lib.show_deps import get_all_modules
37+
38+
all_modules = get_all_modules(cpython_prefix)
39+
40+
# Build dependency data for all modules
41+
module_data = {}
42+
for name in all_modules:
43+
soft_deps = get_soft_deps(name, cpython_prefix)
44+
native_deps = get_rust_deps(name, cpython_prefix)
45+
up_to_date = is_up_to_date(name, cpython_prefix, lib_prefix)
46+
47+
module_data[name] = {
48+
"name": name,
49+
"soft_deps": soft_deps,
50+
"native_deps": native_deps,
51+
"up_to_date": up_to_date,
52+
}
53+
54+
# Build reverse dependency map: who depends on this module
55+
reverse_deps: dict[str, set[str]] = {name: set() for name in all_modules}
56+
for name, data in module_data.items():
57+
for dep in data["soft_deps"]:
58+
if dep in reverse_deps:
59+
reverse_deps[dep].add(name)
60+
61+
# Compute scores and filter
62+
result = []
63+
for name, data in module_data.items():
64+
# Skip already up-to-date modules (unless --done)
65+
if data["up_to_date"] and not include_done:
66+
continue
67+
68+
soft_deps = data["soft_deps"]
69+
if not soft_deps:
70+
# No pylib dependencies
71+
score = -1
72+
total_deps = 0
73+
else:
74+
# Count NOT up-to-date dependencies
75+
score = sum(
76+
1
77+
for dep in soft_deps
78+
if dep in module_data and not module_data[dep]["up_to_date"]
79+
)
80+
total_deps = len(soft_deps)
81+
82+
result.append(
83+
{
84+
"name": name,
85+
"score": score,
86+
"total_deps": total_deps,
87+
"reverse_deps": reverse_deps[name],
88+
"reverse_deps_count": len(reverse_deps[name]),
89+
"native_deps_count": len(data["native_deps"]),
90+
"native_deps": data["native_deps"],
91+
"soft_deps": soft_deps,
92+
"up_to_date": data["up_to_date"],
93+
}
94+
)
95+
96+
# Sort by:
97+
# 1. score (ascending) - fewer outstanding deps first
98+
# 2. reverse_deps_count (descending) - more dependents first
99+
# 3. native_deps_count (ascending) - fewer native deps first
100+
result.sort(
101+
key=lambda x: (
102+
x["score"],
103+
-x["reverse_deps_count"],
104+
x["native_deps_count"],
105+
)
106+
)
107+
108+
return result
109+
110+
111+
def format_todo_list(
112+
todo_list: list[dict],
113+
limit: int | None = None,
114+
verbose: bool = False,
115+
) -> list[str]:
116+
"""Format todo list for display.
117+
118+
Args:
119+
todo_list: List from compute_todo_list()
120+
limit: Maximum number of items to show
121+
verbose: Show detailed dependency information
122+
123+
Returns:
124+
List of formatted lines
125+
"""
126+
lines = []
127+
128+
if limit:
129+
todo_list = todo_list[:limit]
130+
131+
for item in todo_list:
132+
name = item["name"]
133+
score = item["score"]
134+
total_deps = item["total_deps"]
135+
rev_count = item["reverse_deps_count"]
136+
137+
done_mark = "[x]" if item["up_to_date"] else "[ ]"
138+
139+
if score == -1:
140+
score_str = "no deps"
141+
else:
142+
score_str = f"{score}/{total_deps} deps"
143+
144+
rev_str = f"{rev_count} dependents" if rev_count else ""
145+
146+
parts = [done_mark, f"[{score_str}]", name]
147+
if rev_str:
148+
parts.append(f"({rev_str})")
149+
150+
lines.append(" ".join(filter(None, parts)))
151+
152+
# Verbose mode: show detailed dependency info
153+
if verbose:
154+
if item["reverse_deps"]:
155+
lines.append(f" dependents: {', '.join(sorted(item['reverse_deps']))}")
156+
if item["soft_deps"]:
157+
lines.append(f" python: {', '.join(sorted(item['soft_deps']))}")
158+
if item["native_deps"]:
159+
lines.append(f" native: {', '.join(sorted(item['native_deps']))}")
160+
161+
return lines
162+
163+
164+
def show_todo(
165+
cpython_prefix: str = "cpython",
166+
lib_prefix: str = "Lib",
167+
limit: int | None = None,
168+
include_done: bool = False,
169+
verbose: bool = False,
170+
) -> None:
171+
"""Show prioritized list of modules to update."""
172+
todo_list = compute_todo_list(cpython_prefix, lib_prefix, include_done)
173+
for line in format_todo_list(todo_list, limit, verbose):
174+
print(line)
175+
176+
177+
def main(argv: list[str] | None = None) -> int:
178+
parser = argparse.ArgumentParser(
179+
description=__doc__,
180+
formatter_class=argparse.RawDescriptionHelpFormatter,
181+
)
182+
parser.add_argument(
183+
"--cpython",
184+
default="cpython",
185+
help="CPython directory prefix (default: cpython)",
186+
)
187+
parser.add_argument(
188+
"--lib",
189+
default="Lib",
190+
help="Local Lib directory prefix (default: Lib)",
191+
)
192+
parser.add_argument(
193+
"--limit",
194+
type=int,
195+
default=None,
196+
help="Maximum number of items to show",
197+
)
198+
parser.add_argument(
199+
"--done",
200+
action="store_true",
201+
help="Include already up-to-date modules",
202+
)
203+
parser.add_argument(
204+
"--verbose",
205+
"-v",
206+
action="store_true",
207+
help="Show detailed dependency information",
208+
)
209+
210+
args = parser.parse_args(argv)
211+
212+
try:
213+
show_todo(args.cpython, args.lib, args.limit, args.done, args.verbose)
214+
return 0
215+
except Exception as e:
216+
print(f"Error: {e}", file=sys.stderr)
217+
return 1
218+
219+
220+
if __name__ == "__main__":
221+
sys.exit(main())

0 commit comments

Comments
 (0)