Skip to content

Commit a068219

Browse files
committed
fix(website): type build template entries
1 parent 38b54ca commit a068219

2 files changed

Lines changed: 110 additions & 103 deletions

File tree

website/build.py

Lines changed: 70 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
from collections.abc import Sequence
1010
from datetime import UTC, datetime
1111
from pathlib import Path
12-
from typing import Any
12+
from typing import TypedDict
1313

1414
from jinja2 import Environment, FileSystemLoader
15-
from readme_parser import ParsedGroup, ParsedSection, parse_readme, parse_sponsors, slugify
15+
from readme_parser import AlsoSee, ParsedGroup, ParsedSection, parse_readme, parse_sponsors, slugify
1616

1717
GITHUB_REPO_URL_RE = re.compile(r"^https?://github\.com/([^/]+/[^/]+?)(?:\.git)?/?$")
1818
MARKDOWN_LINK_RE = re.compile(r"\[[^\]]+\]\(([^)\s]+)\)")
@@ -36,6 +36,37 @@
3636
}
3737

3838

39+
class TemplateSubcategory(TypedDict):
40+
name: str
41+
value: str
42+
slug: str
43+
url: str
44+
45+
46+
class TemplateEntry(TypedDict):
47+
name: str
48+
url: str
49+
description: str
50+
categories: list[str]
51+
groups: list[str]
52+
subcategories: list[TemplateSubcategory]
53+
stars: int | None
54+
owner: str | None
55+
last_commit_at: str | None
56+
source_type: str | None
57+
also_see: list[AlsoSee]
58+
59+
60+
class SyntheticCategory(TypedDict):
61+
name: str
62+
slug: str
63+
description: str
64+
description_html: str
65+
66+
67+
TemplateCategory = ParsedSection | SyntheticCategory
68+
69+
3970
def detect_source_type(url: str) -> str | None:
4071
"""Detect source type from URL domain. Returns None for GitHub URLs."""
4172
if GITHUB_REPO_URL_RE.match(url):
@@ -64,13 +95,13 @@ def load_stars(path: Path) -> dict[str, dict]:
6495
return {}
6596

6697

67-
def sort_entries(entries: list[dict]) -> list[dict]:
98+
def sort_entries(entries: Sequence[TemplateEntry]) -> list[TemplateEntry]:
6899
"""Sort entries by stars descending, then name ascending.
69100
70101
Three tiers: starred entries first, stdlib second, other non-starred last.
71102
"""
72103

73-
def sort_key(entry: dict) -> tuple[int, int, int, str]:
104+
def sort_key(entry: TemplateEntry) -> tuple[int, int, int, str]:
74105
stars = entry["stars"]
75106
name = entry["name"].lower()
76107
if stars is not None:
@@ -84,13 +115,7 @@ def sort_key(entry: dict) -> tuple[int, int, int, str]:
84115

85116

86117
def build_robots_txt() -> str:
87-
return (
88-
"User-agent: *\n"
89-
"Content-Signal: search=yes, ai-input=yes, ai-train=yes\n"
90-
"Allow: /\n"
91-
"\n"
92-
f"Sitemap: {SITEMAP_URL}\n"
93-
)
118+
return f"User-agent: *\nContent-Signal: search=yes, ai-input=yes, ai-train=yes\nAllow: /\n\nSitemap: {SITEMAP_URL}\n"
94119

95120

96121
def category_path(category: ParsedSection) -> str:
@@ -117,7 +142,7 @@ def subcategory_public_url(category_slug: str, subcategory_slug: str) -> str:
117142
return f"{SITE_URL}categories/{category_slug}/{subcategory_slug}/"
118143

119144

120-
def synthetic_category(name: str, slug: str) -> dict[str, str]:
145+
def synthetic_category(name: str, slug: str) -> SyntheticCategory:
121146
return {"name": name, "slug": slug, "description": "", "description_html": ""}
122147

123148

@@ -202,7 +227,7 @@ def annotate_entries_with_stars(
202227
if not entry or "stars" not in entry:
203228
continue
204229
stripped = line.rstrip("\n")
205-
ending = line[len(stripped):]
230+
ending = line[len(stripped) :]
206231
annotated = f"{stripped} ({format_stars(entry['stars'])}){ending}"
207232
break
208233
out.append(annotated)
@@ -233,35 +258,35 @@ def remove_sponsors_section(markdown: str) -> str:
233258
def extract_entries(
234259
categories: list[ParsedSection],
235260
groups: list[ParsedGroup],
236-
) -> list[dict]:
261+
) -> list[TemplateEntry]:
237262
"""Flatten categories into individual library entries for table display.
238263
239264
Entries appearing in multiple categories are merged into a single entry
240265
with lists of categories and groups.
241266
"""
242267
cat_to_group = {cat["name"]: group["name"] for group in groups for cat in group["categories"]}
243268

244-
seen: dict[tuple[str, str], dict[str, Any]] = {} # (url, name) -> entry
245-
entries: list[dict[str, Any]] = []
269+
seen: dict[tuple[str, str], TemplateEntry] = {} # (url, name) -> entry
270+
entries: list[TemplateEntry] = []
246271
for cat in categories:
247272
group_name = cat_to_group.get(cat["name"], "Other")
248273
for entry in cat["entries"]:
249274
key = (entry["url"], entry["name"])
250-
existing: dict[str, Any] | None = seen.get(key)
275+
existing = seen.get(key)
251276
if existing is None:
252-
existing = {
253-
"name": entry["name"],
254-
"url": entry["url"],
255-
"description": entry["description"],
256-
"categories": [],
257-
"groups": [],
258-
"subcategories": [],
259-
"stars": None,
260-
"owner": None,
261-
"last_commit_at": None,
262-
"source_type": detect_source_type(entry["url"]),
263-
"also_see": entry["also_see"],
264-
}
277+
existing = TemplateEntry(
278+
name=entry["name"],
279+
url=entry["url"],
280+
description=entry["description"],
281+
categories=[],
282+
groups=[],
283+
subcategories=[],
284+
stars=None,
285+
owner=None,
286+
last_commit_at=None,
287+
source_type=detect_source_type(entry["url"]),
288+
also_see=entry["also_see"],
289+
)
265290
seen[key] = existing
266291
entries.append(existing)
267292
if cat["name"] not in existing["categories"]:
@@ -273,12 +298,14 @@ def extract_entries(
273298
scoped = f"{cat['name']} > {subcat}"
274299
if not any(s["value"] == scoped for s in existing["subcategories"]):
275300
sub_slug = slugify(subcat)
276-
existing["subcategories"].append({
277-
"name": subcat,
278-
"value": scoped,
279-
"slug": sub_slug,
280-
"url": f"/categories/{cat['slug']}/{sub_slug}/",
281-
})
301+
existing["subcategories"].append(
302+
TemplateSubcategory(
303+
name=subcat,
304+
value=scoped,
305+
slug=sub_slug,
306+
url=f"/categories/{cat['slug']}/{sub_slug}/",
307+
)
308+
)
282309
return entries
283310

284311

@@ -303,10 +330,7 @@ def build(repo_root: Path) -> None:
303330
all_top_level_slugs = cat_slugs + group_slugs + [BUILTIN_SLUG]
304331
duplicates = {s for s, n in Counter(all_top_level_slugs).items() if n > 1}
305332
if duplicates:
306-
raise ValueError(
307-
f"slug collision in /categories/ namespace: {sorted(duplicates)}. "
308-
"Rename a category or group so their slugs differ."
309-
)
333+
raise ValueError(f"slug collision in /categories/ namespace: {sorted(duplicates)}. Rename a category or group so their slugs differ.")
310334
total_entries = sum(c["entry_count"] for c in categories)
311335
entries = extract_entries(categories, parsed_groups)
312336
build_date = datetime.now(UTC)
@@ -377,14 +401,14 @@ def build(repo_root: Path) -> None:
377401
categories_dir = site_dir / "categories"
378402

379403
def render_category(
380-
category: dict,
404+
category: TemplateCategory,
381405
*,
382406
category_url: str,
383-
entries: list[dict],
407+
entries: Sequence[TemplateEntry],
384408
current_path: str,
385409
page_dir: Path,
386-
parent_category: dict | None = None,
387-
group_categories: list | None = None,
410+
parent_category: ParsedSection | None = None,
411+
group_categories: Sequence[ParsedSection] | None = None,
388412
) -> None:
389413
page_dir.mkdir(parents=True, exist_ok=True)
390414
(page_dir / "index.html").write_text(
@@ -443,7 +467,7 @@ def render_category(
443467
encoding="utf-8",
444468
)
445469

446-
subcat_to_entries: dict[str, list[dict]] = {}
470+
subcat_to_entries: dict[str, list[TemplateEntry]] = {}
447471
subcat_meta: dict[str, tuple[str, str, str]] = {} # value -> (cat_slug, sub_slug, sub_name)
448472
cat_slug_by_url_prefix = {f"/categories/{c['slug']}/": c["slug"] for c in categories}
449473
cat_by_slug = {c["slug"]: c for c in categories}
@@ -472,9 +496,7 @@ def render_category(
472496
if static_src.exists():
473497
shutil.copytree(static_src, static_dst, dirs_exist_ok=True)
474498

475-
markdown_index = annotate_entries_with_stars(
476-
remove_sponsors_section(readme_text), stars_data
477-
)
499+
markdown_index = annotate_entries_with_stars(remove_sponsors_section(readme_text), stars_data)
478500
llms_template = (website / "templates" / "llms.txt").read_text(encoding="utf-8")
479501
llms_txt = build_llms_txt(llms_template, readme_text, stars_data)
480502
(site_dir / "robots.txt").write_text(build_robots_txt(), encoding="utf-8")

0 commit comments

Comments
 (0)