diff --git a/scripts/generate_site.py b/scripts/generate_site.py index 3883f874..4172b93c 100644 --- a/scripts/generate_site.py +++ b/scripts/generate_site.py @@ -2001,25 +2001,41 @@ def file_location_items(db: dict) -> list[dict]: def _group_by_manufacturer(db: dict) -> dict[str, dict[str, list]]: - """Group files by manufacturer -> console -> files.""" - manufacturers: dict[str, dict[str, list]] = {} - for sha1, entry in db.get("files", {}).items(): - path = entry.get("path", "") - parts = path.split("/") - if len(parts) < 3 or parts[0] != "bios": + """Group files by manufacturer -> console -> files. + + The database keeps one path per content, and a copy filed elsewhere was + on no page of its own: the Channel F BIOS sat under Arcade > FBNeo only, + and Fairchild had no system page. Every folder holding a copy lists it, + read from the full paths by_path_suffix records. + """ + files = db.get("files", {}) + bios_dir = Path(str(db.get("bios_dir") or "bios")) + places: dict[str, set[tuple[str, str]]] = {} + for sha1, entry in files.items(): + parts = str(entry.get("path", "")).split("/") + if len(parts) >= 3 and parts[0] == "bios": + places.setdefault(sha1, set()).add((parts[1], parts[2])) + for tail, sha1s in db.get("indexes", {}).get("by_path_suffix", {}).items(): + parts = tail.split("/") + if len(parts) < 3 or parts[1].startswith(".") or len(sha1s) != 1: continue - manufacturer = parts[1] - console = parts[2] - manufacturers.setdefault(manufacturer, {}).setdefault(console, []).append(entry) + if sha1s[0] in files and (bios_dir / tail).is_file(): + places.setdefault(sha1s[0], set()).add((parts[0], parts[1])) + manufacturers: dict[str, dict[str, list]] = {} + for sha1, where in places.items(): + for manufacturer, console in sorted(where): + manufacturers.setdefault(manufacturer, {}).setdefault(console, []).append(files[sha1]) return manufacturers def generate_systems_index(manufacturers: dict) -> str: total_mfr = len(manufacturers) total_consoles = sum(len(c) for c in manufacturers.values()) - total_files = sum( - len(files) for consoles in manufacturers.values() for files in consoles.values() - ) + # A copy filed under two folders appears on both pages, counted once here. + total_files = len({ + f.get("sha1") for consoles in manufacturers.values() + for files in consoles.values() for f in files + }) lines = [ "# Systems", diff --git a/tests/test_site_system_links.py b/tests/test_site_system_links.py index 8327ac88..ca568dbe 100644 --- a/tests/test_site_system_links.py +++ b/tests/test_site_system_links.py @@ -96,5 +96,32 @@ class OtherSpellingsOfOneSystem(unittest.TestCase): self.assertEqual(siterender._system_page_map["commodore-pet"], ("commodore", "PET")) +class ACopyIsOnItsOwnFoldersPage(unittest.TestCase): + """The database keeps one path per content: the Channel F BIOS was on the + Arcade page only, and Fairchild had no system page at all.""" + + def test_every_folder_holding_a_copy_lists_it(self): + import tempfile + + with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp: + for rel in ("Arcade/FBNeo/sl31253.rom", "Fairchild/Channel F/sl31253.bin"): + (Path(tmp) / rel).parent.mkdir(parents=True, exist_ok=True) + (Path(tmp) / rel).write_bytes(b"x") + db = { + "bios_dir": tmp, + "files": {"s": {"path": "bios/Arcade/FBNeo/sl31253.rom", "sha1": "s", + "name": "sl31253.rom"}}, + "indexes": {"by_path_suffix": { + "Arcade/FBNeo/sl31253.rom": ["s"], "FBNeo/sl31253.rom": ["s"], + "Fairchild/Channel F/sl31253.bin": ["s"], "Channel F/sl31253.bin": ["s"], + }}, + } + groups = gs._group_by_manufacturer(db) + self.assertEqual(sorted(groups), ["Arcade", "Fairchild"]) + self.assertEqual(list(groups["Fairchild"]), ["Channel F"]) + # The copy is listed twice and counted once. + self.assertRegex(gs.generate_systems_index(groups), r"2 consoles, 1 files? ") + + if __name__ == "__main__": unittest.main()