From 6fb79cab6d1ffbc895dc32cc19ecd72d5ce73dff Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Thu, 5 Nov 2026 12:15:26 +0100 Subject: [PATCH] fix: redirect every list part that goes away --- scripts/generate_site.py | 55 +++++++++++++++---- tests/test_site_r2_nav.py | 17 ++++++ ...atform-parts.json => published-parts.json} | 0 3 files changed, 60 insertions(+), 12 deletions(-) rename website/src/data/{platform-parts.json => published-parts.json} (100%) diff --git a/scripts/generate_site.py b/scripts/generate_site.py index 8f241f97a..47af25709 100644 --- a/scripts/generate_site.py +++ b/scripts/generate_site.py @@ -3339,18 +3339,49 @@ PLATFORM_PART_BUDGET = 700 PLATFORM_REFERENCE_BUDGET = 600 -# Every part page a platform list has had, with the system it held: -# platforms/batocera-atari-st.md -> platforms/batocera.md#atari-st. When a -# budget or a list changes, a part can fold back into its platform page, -# and its address then redirects there instead of ending on the 404 page. -PLATFORM_PARTS_FILE = (Path(__file__).resolve().parent.parent - / "website" / "src" / "data" / "platform-parts.json") +# Every part page the site has published, with the page it belongs to: +# platforms/batocera-atari-st.md -> platforms/batocera.md#atari-st, +# gaps-files-to-source-7.md -> gaps.md. When a budget or a list changes, a +# part can fold back or the list shrink, and its address then redirects to +# that page instead of ending on the 404 page. +PUBLISHED_PARTS_FILE = (Path(__file__).resolve().parent.parent + / "website" / "src" / "data" / "published-parts.json") -def _published_platform_parts() -> dict[str, str]: - if not PLATFORM_PARTS_FILE.is_file(): +def _published_parts() -> dict[str, str]: + if not PUBLISHED_PARTS_FILE.is_file(): return {} - return json.loads(PLATFORM_PARTS_FILE.read_text(encoding="utf-8")) + return json.loads(PUBLISHED_PARTS_FILE.read_text(encoding="utf-8")) + + +def _parent_page(link: str) -> str: + """The page a `parent:` link names (/platforms/batocera/), under docs/.""" + path, _, fragment = link.partition("#") + stem = path.strip("/") or "index" + return f"{stem}.md" + (f"#{fragment}" if fragment else "") + + +def record_parts(docs: Path, present: set[str], published: dict[str, str]) -> dict[str, str]: + """``published`` with every page of this run that names its parent. + + Only platform parts were recorded: gaps-files-to-source-7 or + mame-files-10, gone once their list shrank, ended on the 404 page. A + part already recorded keeps its target, which may name a section. + """ + found = dict(published) + for rel in sorted(present): + page = docs / rel + if not page.is_file(): + page = page.with_suffix(".mdx") + try: + with page.open(encoding="utf-8") as handle: + head = handle.read(2048) + except OSError: + continue + link = re.search(r"^parent:\n(?: .*\n)*? link: (\"[^\"\n]*\")", head, re.M) + if link and rel not in found: + found[rel] = _parent_page(json.loads(link[1])) + return dict(sorted(found.items())) # A list moved off a reference page (gaps, provenance) is cut in parts of @@ -12156,7 +12187,7 @@ def _generate(args: argparse.Namespace) -> None: ) if HANDHELD_GUIDE: write_if_changed(str(docs / HANDHELD_GUIDE.path), generate_handheld_guide(HANDHELD_GUIDE)) - published_parts = _published_platform_parts() + published_parts = _published_parts() for name, cov in coverages.items(): # The emulators this platform runs, not every one of the system. runs = resolve_platform_cores(cov["config"], profiles) @@ -12345,8 +12376,6 @@ def _generate(args: argparse.Namespace) -> None: stale += _sweep_top_pages(docs) print("Adding page metadata and structured data...") - write_if_changed(str(PLATFORM_PARTS_FILE), - json.dumps(dict(sorted(published_parts.items())), indent=1) + "\n") lastmod = decorate_markdown_pages(docs) nav = generate_nav(coverages, manufacturers, profiles, registry, @@ -12357,6 +12386,8 @@ def _generate(args: argparse.Namespace) -> None: path.relative_to(docs).with_suffix(".md").as_posix() for path in docs.rglob("*.md*") if path.suffix in (".md", ".mdx") } - {"404.md"} + published_parts = record_parts(docs, present, published_parts) + write_if_changed(str(PUBLISHED_PARTS_FILE), json.dumps(published_parts, indent=1) + "\n") generated = site / "src" / "generated" generated.mkdir(parents=True, exist_ok=True) write_if_changed(str(generated / "sidebar.json"), diff --git a/tests/test_site_r2_nav.py b/tests/test_site_r2_nav.py index 6e3f134a7..a0175dcc1 100644 --- a/tests/test_site_r2_nav.py +++ b/tests/test_site_r2_nav.py @@ -279,6 +279,23 @@ class Redirects(unittest.TestCase): source = Path(generate_site.__file__).read_text(encoding="utf-8") self.assertIn('generated / "redirects.json"', source) + def test_a_list_part_gone_with_its_rows_redirects_to_its_page(self): + """Only platform parts were recorded: gaps-files-to-source-7, gone + once 29 files were collected, ended on the 404 page.""" + with tempfile.TemporaryDirectory() as tmp: + docs = Path(tmp) + part = '---\nlevel: reference\nparent:\n label: "Gaps"\n link: "/gaps/"\n---\n\n# P\n' + (docs / "gaps-files-to-source-7.md").write_text(part, encoding="utf-8") + (docs / "gaps.md").write_text("# Gaps\n", encoding="utf-8") + kept = {"platforms/b-atari-st.md": "platforms/b.md#atari-st"} + published = generate_site.record_parts( + docs, {"gaps-files-to-source-7.md", "gaps.md"}, kept) + self.assertEqual(published, {"gaps-files-to-source-7.md": "gaps.md", **kept}) + # The next run made six parts. + self.assertEqual( + sitestarlight.redirects({}, {"gaps.md", "platforms/b.md"}, published), + {"/gaps-files-to-source-7/": "/gaps/", "/platforms/b-atari-st/": "/platforms/b/#atari-st"}) + class PlatformShareCards(unittest.TestCase): def test_every_platform_page_shares_its_own_card(self): diff --git a/website/src/data/platform-parts.json b/website/src/data/published-parts.json similarity index 100% rename from website/src/data/platform-parts.json rename to website/src/data/published-parts.json