feat: name the files in emulator search snippets

A search for gba_bios.bin or scph5500.bin landed on a page described as
"It reads 4 files, none of them required". The snippet now names the
console and the files, required first, in the longest form that fits.
This commit is contained in:
Abdessamad Derraz committed 2026-11-06 09:28:03 +01:00
1 parent 19898cb6ea
commit 79a21ba1f1
2 files changed
+91 -1

No files matched your search

+63 -1
View File
@@ -2591,8 +2591,10 @@ def _emulator_description(markdown: str, key: str, title: str, intro: str) -> st
intro = f"{title}: {intro[:1].lower()}{intro[1:]}"
if key in NOTHING_TO_FETCH:
return _shorten(f"{intro} Every file it reads comes with the app.", 155)
if PAGE_DATA.get(key, {}).get("hasPart"):
return _shorten(_files_snippet(markdown, key, title, intro), 155)
# An alias page sends the reader to the core whose files it shares.
if PAGE_DATA.get(key, {}).get("hasPart") or "uses the same firmware as" in intro:
if "uses the same firmware as" in intro:
return _shorten(intro, 155)
needs = "It needs no BIOS or firmware file."
if "\n## Data directories" in markdown:
@@ -2600,6 +2602,44 @@ def _emulator_description(markdown: str, key: str, title: str, intro: str) -> st
return _shorten(f"{intro} {needs}", 155)
def _files_snippet(markdown: str, key: str, title: str, intro: str) -> str:
"""The snippet of an emulator page with files to get.
A search for "gba_bios.bin" or "scph5500.bin" landed on a page described
as "It reads 4 files, none of them required": the names the reader typed
were not in the result, and fewer than 1 in 70 clicked. The longest
form that fits is kept: a snippet cut by "..." reads as unfinished.
"""
if " any region." in intro:
step = ("Download one from this page and put it in the folder it shows."
if re.search(r"^1\. Download ", _lead(markdown), re.M)
else "Where to get one and the folder it goes in.")
head = f"{intro.split(' any region.')[0]} any region."
forms = [f"{head} {step} Any file name works.", f"{head} {step}"]
return next((form for form in forms if len(form) <= 155), intro)
snippet = EMULATOR_SNIPPETS.get(key)
if not snippet or not snippet[1] or "It reads " not in intro:
return intro
system, names, required, noun = snippet
if required == 0:
need = "none required"
elif required == len(names):
need = "all required" if len(names) > 1 else "required"
else:
need = f"{required:,} required"
subject = f"{system} {noun} for {title}" if system else f"{title} {noun}"
tails = ["Where each goes, its hashes and one command that installs them."
if "Install this emulator's files" in markdown else "",
"Where each goes and its hashes.", "Where each goes."]
forms = []
for count in range(min(len(names), 4), 0, -1):
shown = (names if count == len(names)
else [*names[:count - 1], f"{len(names) - count + 1:,} more"])
head = f"{subject}: {_said_as_list(shown)}, {need}."
forms += [f"{head} {tail}" for tail in tails if tail] + [head]
return next((form for form in forms if len(form) <= 155), intro)
# Lines a page opens with that say where it sits, not what it holds.
_PLACE_LINES = ("Part of ", "This part holds ", "On a card, ")
@@ -2664,6 +2704,9 @@ PAGE_DATA: dict[str, dict] = {}
# Emulator pages whose every file comes with the app: no "BIOS files" in
# their title, and their description says so (DaedalusX64, ZEsarUX).
NOTHING_TO_FETCH: set[str] = set()
# The first system, the file names, the count of required names and the
# word for them ("BIOS" or "files") of each emulator page, for its snippet.
EMULATOR_SNIPPETS: dict[str, tuple[str, list[str], int, str]] = {}
# Pages that hold part of a page too long for one (a console of a split
# manufacturer, a run of an emulator's cards), by path relative to docs/:
# their H1 already says what they are.
@@ -2996,6 +3039,25 @@ def record_emulator_page(name: str, profile: dict) -> None:
key = f"emulators/{name}.md"
PAGE_DATA[key] = emulator_structured_data(profile)
listed = [f for f in profile.get("files") or [] if isinstance(f, dict)]
fetched = [f for f in listed if f.get("name") and not comes_with_app(f)]
systems = profile.get("systems") or []
first = systems[0] if systems else ""
# The first system's files first: "dreamcast bios" found Flycast's
# page, whose required NAOMI archives came before dc_boot.bin.
names: dict[str, str] = {}
for f in sorted(fetched, key=lambda f: (f.get("system", first) != first,
f.get("required") is not True)):
base = Path(str(f["name"])).name
names.setdefault(base.casefold(), base)
firmware = [f for f in fetched if f.get("category", "bios") in ("bios", "bios_zip")]
EMULATOR_SNIPPETS[key] = (
# One console names an emulator of five systems, not BizHawk of 40.
system_name(first) if 0 < len(systems) <= 5 else "",
list(names.values()),
len({Path(str(f["name"])).name.casefold() for f in fetched if f.get("required") is True}),
# Ship of Harkinian reads game ROMs: they are no BIOS.
"BIOS" if len(firmware) * 2 >= len(fetched) else "files",
)
if listed and all(comes_with_app(f) for f in listed):
NOTHING_TO_FETCH.add(key)
else:
+28
View File
@@ -245,6 +245,34 @@ class Descriptions(unittest.TestCase):
self.assertEqual(gs._page_description(body, key, "DaedalusX64"),
"DaedalusX64 emulates Nintendo 64. Every file it reads comes with the app.")
def test_an_emulator_snippet_names_the_files_searched_for(self) -> None:
""""gba_bios.bin" and "dreamcast bios" landed on pages described as
"It reads 4 files, none of them required": fewer than 1 in 70 clicked."""
saved, snippets = dict(gs.PAGE_DATA), dict(gs.EMULATOR_SNIPPETS)
self.addCleanup(lambda: (gs.PAGE_DATA.clear(), gs.PAGE_DATA.update(saved),
gs.EMULATOR_SNIPPETS.clear(),
gs.EMULATOR_SNIPPETS.update(snippets)))
with open(REPO_ROOT / "emulators" / "flycast.yml", encoding="utf-8") as handle:
gs.record_emulator_page("flycast", yaml.safe_load(handle))
key = Path("emulators/flycast.md")
body = ("# Flycast\n\nFlycast emulates Sega Dreamcast. It reads 10 files, 4 of them "
"required: without a required file it does not start games.\n")
text = gs._page_description(body, key, "Flycast")
self.assertTrue(text.startswith("Sega Dreamcast BIOS for Flycast: dc_boot.bin, "), text)
self.assertLessEqual(len(text), 155)
self.assertFalse(text.endswith("..."), text)
def test_an_any_name_snippet_says_what_to_do(self) -> None:
saved = dict(gs.PAGE_DATA)
self.addCleanup(lambda: (gs.PAGE_DATA.clear(), gs.PAGE_DATA.update(saved)))
gs.PAGE_DATA["emulators/aethersx2.md"] = {"hasPart": [{"name": "ps2.bin"}]}
body = ("# AetherSX2\n\nAetherSX2 needs one Sony PlayStation 2 BIOS file, any region."
"\n\n1. Download [`ps2.bin`](../x/).\n2. Put it in this folder:\n")
self.assertEqual(
gs._page_description(body, Path("emulators/aethersx2.md"), "AetherSX2"),
"AetherSX2 needs one Sony PlayStation 2 BIOS file, any region. Download one "
"from this page and put it in the folder it shows. Any file name works.")
def test_an_initial_does_not_end_the_sentence(self) -> None:
"""hcl's description was "Hydra Castle Labyrinth: open-source port of E."."""
body = ("# Hydra Castle Labyrinth\n\n<details>\n<summary>Technical notes</summary>\n\n"