From 91403aa14949c2747b1fbfd82db4a544c116ac4a Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Tue, 20 Oct 2026 04:55:16 +0200 Subject: [PATCH] fix: site systems match under every spelling Platform pages matched emulators to systems by the exact id, so the RetroArch page listed none for sega-mega-drive (genesis_plus_gx says sega-megadrive), and listed every emulator of a system instead of those the platform runs. Emulator cards linked only ids a platform uses: 2,057 of 4,215 system lines had no link, now 327, the rest having no page. Ids are compared as _norm_system_id reads them, and a system only profiles name is placed by the files they file under it. --- scripts/generate_site.py | 64 +++++++++++++++++++++++++-------- scripts/siterender.py | 31 ++++++++++++++-- tests/test_site_system_links.py | 40 +++++++++++++++++++++ 3 files changed, 119 insertions(+), 16 deletions(-) diff --git a/scripts/generate_site.py b/scripts/generate_site.py index 48f8a4c0..2ee9a1b7 100644 --- a/scripts/generate_site.py +++ b/scripts/generate_site.py @@ -146,6 +146,7 @@ def _build_system_page_map_from_data( manufacturers: dict, coverages: dict, db: dict, + profiles: dict | None = None, ) -> None: """Build system_id -> (manufacturer_slug, console_name) mapping. @@ -210,6 +211,26 @@ def _build_system_page_map_from_data( _system_page_map[sys_id] = console_norm[suffix] break + # Systems only emulator profiles name (sega-naomi, commodore-pet): placed + # by the files the profile files under them, as a platform's are. A + # profile of one system files all of its entries there. + for profile in (profiles or {}).values(): + systems = [str(sid) for sid in profile.get("systems") or []] + for sys_id in systems: + if sys_id in _system_page_map: + continue + files = [ + entry for entry in profile.get("files") or [] + if isinstance(entry, dict) + and (entry.get("system") == sys_id or len(systems) == 1) + ] + place = _system_folder(files, db, name_consoles) + if place is not None: + mfr, console = place + _system_page_map[sys_id] = (mfr.lower().replace(" ", "-"), console) + elif sys_id in console_norm: + _system_page_map[sys_id] = console_norm[sys_id] + @@ -1680,11 +1701,7 @@ def _render_platform_systems( ok_count = sum(1 for f in files if f["status"] == "ok") total = len(files) - sys_emus = [] - if emulator_files: - for emu_name, emu_data in emulator_files.items(): - if sys_id in emu_data.get("systems", set()): - sys_emus.append(emu_name) + sys_emus = _system_emulators(sys_id, emulator_files) sys_link = _system_link(sys_id, "../") @@ -1914,11 +1931,7 @@ def generate_platform_page( f'' f'{non_ok} issue{"s" if non_ok > 1 else ""}' ) - sys_emus = [] - if emulator_files: - for emu_name, emu_data in emulator_files.items(): - if sys_id in emu_data.get("systems", set()): - sys_emus.append(emu_name) + sys_emus = _system_emulators(sys_id, emulator_files) emu_str = ", ".join(sys_emus[:3]) if len(sys_emus) > 3: emu_str += f" +{len(sys_emus) - 3}" @@ -4108,14 +4121,33 @@ def _build_file_consumers(db: dict, coverages: dict, profiles: dict) -> FileCons def _build_emulator_file_index(profiles: dict) -> dict[str, dict]: - """Map emulator_name -> {systems: set}, for the platform pages.""" + """Map emulator_name -> {systems, norm}, for the platform pages. + + norm holds the systems as _norm_system_id reads them: a platform files + the Mega Drive as sega-mega-drive and genesis_plus_gx as sega-megadrive, + and an exact match left the RetroArch page with no emulator for it. + """ + from common import _norm_system_id + return { - name: {"systems": set(profile.get("systems", []))} + name: { + "systems": set(profile.get("systems", [])), + "norm": {_norm_system_id(str(s)) for s in profile.get("systems", [])}, + } for name, profile in profiles.items() if profile.get("type") != "alias" } +def _system_emulators(sys_id: str, emulator_files: dict | None) -> list[str]: + """The emulators a platform runs for one of its systems.""" + from common import _norm_system_id + + norm = _norm_system_id(sys_id) + return [name for name, data in (emulator_files or {}).items() + if sys_id in data["systems"] or norm in data["norm"]] + + def _mkdocs_config(docs: Path, text: str) -> tuple[Path, str]: """Where the site's config goes, and its text for that place. @@ -4533,7 +4565,7 @@ def _generate(args: argparse.Namespace) -> None: # Build system_id -> manufacturer page map (needed by all generators) print("Building system cross-reference map...") manufacturers = _group_by_manufacturer(db) - _build_system_page_map_from_data(manufacturers, coverages, db) + _build_system_page_map_from_data(manufacturers, coverages, db, profiles) print(f" {len(_system_page_map)} system IDs mapped to pages") # Generate platform pages @@ -4543,9 +4575,13 @@ def _generate(args: argparse.Namespace) -> None: generate_platform_index(coverages, registry), ) for name, cov in coverages.items(): + # The emulators this platform runs, not every one of the system. + runs = resolve_platform_cores(cov["config"], profiles) write_if_changed( str(docs / "platforms" / f"{name}.md"), - generate_platform_page(name, cov, registry, emulator_files), + generate_platform_page(name, cov, registry, { + emu: data for emu, data in emulator_files.items() if emu in runs + }), ) PAGE_DATA[f"platforms/{name}.md"] = platform_structured_data(name, cov) diff --git a/scripts/siterender.py b/scripts/siterender.py index 58393157..a1999d01 100644 --- a/scripts/siterender.py +++ b/scripts/siterender.py @@ -128,10 +128,37 @@ def _slugify_anchor(text: str) -> str: slug = slug.strip("-") return slug +_norm_page_cache: dict[str, tuple[str, str] | None] = {} +_norm_page_size = -1 + + +def _page_by_norm(sys_id: str) -> tuple[str, str] | None: + """The page an id leads to under another spelling of the same system. + + Emulator cards name sega-megadrive or sinclair-zxspectrum, the pages are + keyed by the platforms' spellings: 2,431 of 4,195 cards had no link. A + spelling two pages share leads nowhere rather than to either. + """ + global _norm_page_size + from common import _norm_system_id + + if _norm_page_size != len(_system_page_map): + _norm_page_cache.clear() + for known, page in _system_page_map.items(): + key = _norm_system_id(known) + if _norm_page_cache.get(key, page) != page: + _norm_page_cache[key] = None + else: + _norm_page_cache[key] = page + _norm_page_size = len(_system_page_map) + return _norm_page_cache.get(_norm_system_id(sys_id)) + + def _system_link(sys_id: str, prefix: str = "") -> str: """Generate a markdown link to a system page with anchor.""" - if sys_id in _system_page_map: - slug, console = _system_page_map[sys_id] + page = _system_page_map.get(sys_id) or _page_by_norm(sys_id) + if page: + slug, console = page anchor = _slugify_anchor(console) return f"[{sys_id}]({prefix}systems/{slug}.md#{anchor})" return sys_id diff --git a/tests/test_site_system_links.py b/tests/test_site_system_links.py index b2d9bc0b..8327ac88 100644 --- a/tests/test_site_system_links.py +++ b/tests/test_site_system_links.py @@ -56,5 +56,45 @@ class LinksFollowContent(unittest.TestCase): self.assertEqual(place and place[1], console) +class OtherSpellingsOfOneSystem(unittest.TestCase): + """Platforms and profiles spell systems their own way: the RetroArch page + listed no emulator for sega-mega-drive although genesis_plus_gx declares + sega-megadrive, and 2,057 of 4,215 emulator system lines had no link.""" + + def test_a_platform_system_finds_its_emulators(self): + index = gs._build_emulator_file_index({ + "genesis_plus_gx": {"systems": ["sega-megadrive"]}, + "np2kai": {"systems": ["pc-98"]}, + }) + self.assertEqual(gs._system_emulators("sega-mega-drive", index), ["genesis_plus_gx"]) + self.assertEqual(gs._system_emulators("nintendo-snes", index), []) + + def test_a_profile_spelling_links_to_the_platform_page(self): + import siterender + + saved = dict(siterender._system_page_map) + self.addCleanup(lambda: (siterender._system_page_map.clear(), + siterender._system_page_map.update(saved))) + siterender._system_page_map.clear() + siterender._system_page_map["sega-mega-drive"] = ("sega", "Mega Drive") + self.assertIn("systems/sega.md#mega-drive", siterender._system_link("sega-megadrive")) + siterender._system_page_map["megadrive"] = ("other", "Elsewhere") + self.assertEqual(siterender._system_link("sega-megadrive"), "sega-megadrive") + + def test_a_system_only_profiles_name_is_placed_by_their_files(self): + import siterender + + saved = dict(siterender._system_page_map) + self.addCleanup(lambda: (siterender._system_page_map.clear(), + siterender._system_page_map.update(saved))) + siterender._system_page_map.clear() + manufacturers = {"Commodore": {"PET": [{"name": "basic4.bin"}]}} + profiles = {"vice_xpet": {"systems": ["commodore-pet"], + "files": [{"name": "basic4.bin"}]}} + gs._build_system_page_map_from_data( + manufacturers, {}, {"files": {}, "indexes": {}}, profiles) + self.assertEqual(siterender._system_page_map["commodore-pet"], ("commodore", "PET")) + + if __name__ == "__main__": unittest.main()