From 5da589bcaa1adfb2563ed8de946f41a9689a0076 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 23 Oct 2026 02:43:24 +0200 Subject: [PATCH] feat: file emulator folders apart on systems index Other holds one folder per emulator, so it gets its own table. The file count comes before the maker so it stays on a phone screen, and a card's searched label stays out of the search excerpt. --- scripts/generate_site.py | 44 ++++++++++++++++++++++------------- tests/test_site_r2_systems.py | 20 ++++++++++------ tests/test_site_render.py | 10 ++++---- 3 files changed, 46 insertions(+), 28 deletions(-) diff --git a/scripts/generate_site.py b/scripts/generate_site.py index c3414d0a..aa8808fe 100644 --- a/scripts/generate_site.py +++ b/scripts/generate_site.py @@ -2931,35 +2931,44 @@ def generate_systems_index(manufacturers: dict) -> str: "", ] - for heading, games, first in (("Consoles and computers", False, "Console"), - ("Game engines and PC games", True, "Name")): + # Other holds one folder per emulator: a7800 or altirra in the console + # table named no console. + def kind(mfr: str) -> str: + if mfr in GAME_DATA_TOPS: + return "games" + return "programs" if mfr == "Other" else "consoles" + + for heading, group, first, note in ( + ("Consoles and computers", "consoles", "Console", ""), + ("Emulator folders", "programs", "Folder", + "Files kept under the emulator that reads them, not under a console."), + ("Game engines and PC games", "games", "Name", + "Game engines and PC games hold the files a game engine reads, such " + "as the data of a game, rather than the BIOS of a machine."), + ): rows = sorted( ((_console_display(mfr, console), mfr, console, len(files)) - for mfr, consoles in manufacturers.items() - if (mfr in GAME_DATA_TOPS) == games + for mfr, consoles in manufacturers.items() if kind(mfr) == group for console, files in consoles.items()), key=lambda row: (row[0].casefold(), row[1].casefold(), row[2]), ) if not rows: continue + lines += [f"## {heading}", ""] + if note: + lines += [note, ""] lines += [ - f"## {heading}", - "", - f"| {first} | Manufacturer | Files |", - "|---------|--------------|------:|", + # The count before the maker: on a phone the last column is + # the one scrolled off, and most names carry the maker already. + f"| {first} | Files | Maker |", + "|---------|------:|-------|", ] for name, mfr, console, count in rows: cell = name.replace("|", "-") anchor = _slugify_anchor(console) href = f"{_slug(mfr)}.md" + (f"#{anchor}" if anchor else "") - lines.append(f"| [{cell}]({href}) | [{mfr}]({_slug(mfr)}.md) | {count:,} |") + lines.append(f"| [{cell}]({href}) | {count:,} | [{mfr}]({_slug(mfr)}.md) |") lines.append("") - if games: - lines += [ - "Game engines and PC games hold the files a game engine reads, such " - "as the data of a game, rather than the BIOS of a machine.", - "", - ] return "\n".join(lines) + "\n" @@ -3467,7 +3476,10 @@ def _system_file_card( if group: lines.append(f'
') # One value cell per label: the grid pairs each dt with one dd. - lines.extend(f"
{label}
" + # A searched value keeps its label out: the excerpt ran the two + # together, "On the setup's listAmberELEC". + label_skip = f" {skip}" if not attributes else "" + lines.extend(f"{label}
" + "
".join(_inline_card_html(v) for v in values) + "
" for label, values, _searched in group) lines.append("
") diff --git a/tests/test_site_r2_systems.py b/tests/test_site_r2_systems.py index 04cf75ae..7f2e2aa6 100644 --- a/tests/test_site_r2_systems.py +++ b/tests/test_site_r2_systems.py @@ -155,8 +155,9 @@ class TheCardLinesSayWhatTheyMean(unittest.TestCase): def test_labels_and_display_names(self): text = "\n".join(self.lines) - self.assertIn("
On the setup's list
Batocera: scph5501.bin
", text) + self.assertIn("
On the setup's list
Batocera: scph5501.bin
", + text) self.assertIn("
Packed for its emulators by
RetroBat: bios/scph5501.bin
", text) self.assertNotIn("Platforms:", text) @@ -168,6 +169,9 @@ class TheCardLinesSayWhatTheyMean(unittest.TestCase): searched = "\n".join(self.lines).split('
', 1)[1].split("
")[0] self.assertIn("On the setup's list", searched) self.assertNotIn("SHA1", searched) + # The label stays out of the excerpt: the search ran the two + # together, "On the setup's listAmberELEC". + self.assertIn("
On the setup's list
", searched) def test_the_page_defines_both_lines(self): page = gs.generate_system_page("Sony", {"PlayStation": [_file("scph5501.bin", "4")]}, @@ -235,12 +239,14 @@ class TheSystemsIndexListsConsoles(unittest.TestCase): "Game Engines": {"TheXTech": [_file("d.png", "d")]}, } page = gs.generate_systems_index(groups) - self.assertIn("| [Sony PlayStation](sony.md#playstation) | [Sony](sony.md) | 1 |", page) - self.assertIn("| [Sony PlayStation 2](sony.md#playstation-2) | [Sony](sony.md) | 1 |", + self.assertIn("| [Sony PlayStation](sony.md#playstation) | 1 | [Sony](sony.md) |", page) + self.assertIn("| [Sony PlayStation 2](sony.md#playstation-2) | 1 | [Sony](sony.md) |", page) - # A program's folder under Other is named alone. - self.assertIn("| [ScummVM](other.md#scummvm) | [Other](other.md) | 1 |", page) - consoles = page.split("## Game engines")[0] + # A program's folder under Other is named alone, in its own table. + consoles = page.split("## Emulator folders")[0] + self.assertNotIn("ScummVM", consoles) + self.assertIn("| [ScummVM](other.md#scummvm) | 1 | [Other](other.md) |", + page.split("## Emulator folders")[1]) self.assertNotIn("TheXTech", consoles) self.assertIn("| [TheXTech](game-engines.md#thextech)", page) diff --git a/tests/test_site_render.py b/tests/test_site_render.py index ad238838..c957d34c 100644 --- a/tests/test_site_render.py +++ b/tests/test_site_render.py @@ -2,7 +2,6 @@ from __future__ import annotations -import re import sys import tempfile import unittest @@ -357,13 +356,14 @@ class TheSearchSkipsLinkLists(unittest.TestCase): entry = {"name": "a.bin", "sha1": sha1, "md5": "b" * 32, "size": 3} lines = generate_site._system_file_card(entry, consumers) self.assertIn("a.bin", lines[2]) - rows = {re.search(r"
(.*?)
", line)[1]: line for line in lines - if line.startswith("" in line} + text = "\n".join(lines) + searched, _sep, skipped = text.partition('
') for label in ("MD5", "Emulators", "Packed for its emulators by"): - self.assertTrue(rows[label].startswith("
"), label) + self.assertIn(f"
{label}
", skipped) + self.assertNotIn(f">{label}", searched) # The platforms line names the file as each setup does: the site # search reads it, so a file name finds its card. - self.assertTrue(rows["On the setup's list"].startswith("
")) + self.assertRegex(searched, r'
\n]*>On the setup\'s list
') def test_a_game_data_card_skips_its_name_too(self): import generate_site