mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-01 00:27:20 -05:00
feat: download button on every file card
The Apple II page named its start files with no visible way to get one: the download sat after the collection path, fourth of a card's facts. Each card now carries a Download button in its title, each start-line name links to its card, and a card held on another part page is reached by its permanent link.
This commit is contained in:
1 parent
0c77ef074e
commit
b24fc06e51
6 files changed
+133
-26
No files matched your search
@@ -3007,6 +3007,26 @@ def _front_matter(markdown: str) -> dict:
|
||||
return data if isinstance(data, dict) else {}
|
||||
|
||||
|
||||
_SHA1_BY_PREFIX: dict[str, str] = {}
|
||||
|
||||
|
||||
def _cards_elsewhere(markdown: str) -> str:
|
||||
"""A start line links each file to its card. A console split over part
|
||||
pages keeps its cards on the parts, so a card this page does not hold
|
||||
is reached by its permanent link instead."""
|
||||
links = set(re.findall(r"\]\(#(sha1-[0-9a-f]{12})\)", markdown))
|
||||
away = {ident for ident in links if f'id="{ident}"' not in markdown}
|
||||
if not away:
|
||||
return markdown
|
||||
if not _SHA1_BY_PREFIX:
|
||||
db = load_database(str(_REPO_ROOT / "database.json"))
|
||||
_SHA1_BY_PREFIX.update({sha1[:12]: sha1 for sha1 in db["files"]})
|
||||
for ident in away:
|
||||
full = _SHA1_BY_PREFIX[ident.removeprefix("sha1-")]
|
||||
markdown = markdown.replace(f"](#{ident})", f"]({markup.site_href(f'files/?sha1={full}')})")
|
||||
return markdown
|
||||
|
||||
|
||||
def decorate_markdown_pages(docs: Path) -> dict[str, str]:
|
||||
"""Add per-page descriptions and machine-readable structured data.
|
||||
|
||||
@@ -3026,7 +3046,7 @@ def decorate_markdown_pages(docs: Path) -> dict[str, str]:
|
||||
# Written in its final form by an earlier pass and rewritten by
|
||||
# nothing since: a second pass would find no title to read.
|
||||
continue
|
||||
markdown = prepare_page(original, relative)
|
||||
markdown = _cards_elsewhere(prepare_page(original, relative))
|
||||
for image_id in unresolved_images(markdown):
|
||||
print(f" WARNING: {relative}: no diagram named {image_id!r}", file=sys.stderr)
|
||||
stripped = _undecorated(markdown)
|
||||
@@ -5477,8 +5497,14 @@ def _system_file_card(
|
||||
# emulator, platform or pack line and looked unfinished.
|
||||
unread = (f' <span class="rb-badge rb-badge-muted" {skip}>No reader</span>'
|
||||
if not (emus or plats or installed) else "")
|
||||
# The download leads the card: as a "(download)" after the collection
|
||||
# path, fourth of the facts, a reader of the Apple II page found none.
|
||||
download = (
|
||||
f' <a class="rb-download" href="{html.escape(_fetch_url(f["path"]))}" '
|
||||
f'aria-label="Download {html.escape(name)}" {skip}>Download</a>'
|
||||
) if f.get("path") and markup.starlight() else ""
|
||||
lines.append(_card_title(
|
||||
f'{_code(name)} <span class="rb-badge rb-badge-muted">{size}</span>{unread}{permalink}',
|
||||
f'{_code(name)} <span class="rb-badge rb-badge-muted">{size}</span>{unread}{download}{permalink}',
|
||||
anchor, searchable,
|
||||
))
|
||||
lines.append("")
|
||||
@@ -5509,8 +5535,10 @@ def _system_file_card(
|
||||
# The card named a file, its hashes and provenance, but not where to
|
||||
# get it: a user missing scph5501.bin found the reference and no way
|
||||
# to the bytes.
|
||||
facts.append(("Collection path",
|
||||
[f"{_code(f['path'])} ([download]({_fetch_url(f['path'])}))"], False))
|
||||
path = _code(f["path"])
|
||||
if not markup.starlight():
|
||||
path += f" ([download]({_fetch_url(f['path'])}))"
|
||||
facts.append(("Collection path", [path], False))
|
||||
catalogued = [line.split(": ", 1)[1]
|
||||
for line in _provenance_lines(f.get("provenance") or {}, "../")]
|
||||
if catalogued:
|
||||
@@ -6196,7 +6224,18 @@ def _start(
|
||||
sha1 = sha1s.get(visible)
|
||||
if sha1 and sha1 not in order:
|
||||
order.append(sha1)
|
||||
return lines, order
|
||||
return [_link_held_names(line, sha1s, held) for line in lines], order
|
||||
|
||||
|
||||
def _link_held_names(line: str, sha1s: dict[str, str], held: set[str]) -> str:
|
||||
"""Each file the start line names leads to its card, where it downloads:
|
||||
the Apple II line named eight files and none was a way to get one."""
|
||||
def link(match: re.Match) -> str:
|
||||
sha1 = sha1s.get(match[1], "")
|
||||
return f"[{match[0]}](#sha1-{sha1[:12]})" if sha1 in held else match[0]
|
||||
# A name inside a link already (a "Keep it zipped" archive) stays as
|
||||
# is, and so does the name a file is read under, "(as `d2fdc.zip`)".
|
||||
return re.sub(r"(?<!\[)(?<!\(as )`([^`]*)`(?!\]\()", link, line)
|
||||
|
||||
|
||||
def _group_lines(
|
||||
|
||||
@@ -100,17 +100,17 @@ class TheConsoleBiosComesFirst(unittest.TestCase):
|
||||
def test_the_section_opens_on_the_bios_by_region(self):
|
||||
lines = gs._start_with(self.files, self.required_only())
|
||||
self.assertEqual(lines[0], "**Start with** the BIOS for the region of your games:")
|
||||
self.assertIn("- **North America**: `scph5501.bin`", lines)
|
||||
self.assertIn("- **Japan**: `scph5500.bin`", lines)
|
||||
self.assertLess(lines.index("- **North America**: `scph5501.bin`"),
|
||||
lines.index("- **Japan**: `scph5500.bin`"))
|
||||
self.assertIn("- **North America**: [`scph5501.bin`](#sha1-444444444444)", lines)
|
||||
self.assertIn("- **Japan**: [`scph5500.bin`](#sha1-555555555555)", lines)
|
||||
self.assertLess(lines.index("- **North America**: [`scph5501.bin`](#sha1-444444444444)"),
|
||||
lines.index("- **Japan**: [`scph5500.bin`](#sha1-555555555555)"))
|
||||
|
||||
def test_regions_needing_the_same_file_share_a_line(self):
|
||||
consumers = _consumers({"4" * 40: [
|
||||
{"required": True, "region": ["north-america", "latin-america", "brazil"]}]})
|
||||
lines = gs._start_with([_file("scph5501.bin", "4")], consumers)
|
||||
# Brazil says nothing Latin America does not.
|
||||
self.assertIn("- **North America, Latin America**: `scph5501.bin`", lines)
|
||||
self.assertIn("- **North America, Latin America**: [`scph5501.bin`](#sha1-444444444444)", lines)
|
||||
|
||||
def test_a_long_list_names_the_first_and_counts_the_rest(self):
|
||||
files = [_file(f"rom{i}.bin", "abcdefgh"[i]) for i in range(8)]
|
||||
@@ -120,7 +120,8 @@ class TheConsoleBiosComesFirst(unittest.TestCase):
|
||||
consumers.emulator_files = {"emu": [{"required": True}] * len(files)}
|
||||
consumers.emulator_entries = {f["sha1"]: {"emu": i} for i, f in enumerate(files)}
|
||||
self.assertEqual(gs._start_with(files, consumers)[0],
|
||||
"**Start with**: `rom0.bin`, `rom1.bin`, `rom2.bin` and 5 more files. "
|
||||
"**Start with**: [`rom0.bin`](#sha1-aaaaaaaaaaaa), [`rom1.bin`](#sha1-bbbbbbbbbbbb), "
|
||||
"[`rom2.bin`](#sha1-cccccccccccc) and 5 more files. "
|
||||
"They come first below.")
|
||||
|
||||
def test_no_required_file_no_line(self):
|
||||
|
||||
@@ -80,18 +80,18 @@ class TheSectionStartsWithItsOwnConsole(unittest.TestCase):
|
||||
def test_a_console_its_emulators_run_without_says_so(self) -> None:
|
||||
self.assertEqual(gs._start_with(self.files, self.consumers, section=DS), [
|
||||
"**No file needed** with emu-a or emu-b. For full accuracy: "
|
||||
"`bios7.bin`, `bios9.bin`.", ""])
|
||||
"[`bios7.bin`](#sha1-777777777777), [`bios9.bin`](#sha1-999999999999).", ""])
|
||||
|
||||
def test_the_dsi_section_names_its_file(self) -> None:
|
||||
lines = gs._start_with(self.files, self.consumers, section=("Nintendo", "Nintendo DSi"))
|
||||
self.assertEqual(lines, ["**Start with**: `dsi_bios7.bin`.", ""])
|
||||
self.assertEqual(lines, ["**Start with**: [`dsi_bios7.bin`](#sha1-dddddddddddd).", ""])
|
||||
|
||||
def test_more_than_three_emulators_are_counted(self) -> None:
|
||||
hle = {"required": False, "hle_fallback": True, "system": "nintendo-ds"}
|
||||
consumers = _consumers({"7" * 40: [(f"emu-{n}", hle) for n in "abcd"]})
|
||||
self.assertEqual(gs._start_with(self.files[:1], consumers, section=DS)[0],
|
||||
"**No file needed**: none of its 4 emulators requires one. "
|
||||
"For full accuracy: `NDS_Bios7.bin`.")
|
||||
"For full accuracy: [`NDS_Bios7.bin`](#sha1-777777777777).")
|
||||
|
||||
def test_a_shipped_file_is_no_start(self) -> None:
|
||||
consumers = _consumers({"7" * 40: [("emu-a", {"required": True, "bundled": True,
|
||||
@@ -108,7 +108,7 @@ class TheNameSetupsWrite(unittest.TestCase):
|
||||
destinations={"a" * 40: {"p1": {"gba_bios.bin"}, "p2": {"bios/gba_bios.bin"},
|
||||
"p3": {"GBA_bios.rom"}}})
|
||||
self.assertEqual(gs._start_with([f], consumers, section=("Nintendo", "Game Boy Advance")),
|
||||
["**Start with**: `gba_bios.bin`.", ""])
|
||||
["**Start with**: [`gba_bios.bin`](#sha1-aaaaaaaaaaaa).", ""])
|
||||
|
||||
def test_no_setup_then_the_emulators_name(self) -> None:
|
||||
f = _file("x.rom", "b", "bios/Nintendo/Game Boy Advance/x.rom")
|
||||
@@ -142,7 +142,7 @@ class ArcadeSetsAreNamedWhereverFiled(unittest.TestCase):
|
||||
link = f"[`neogeo.zip`](/retrobios/files/?sha1={'2' * 40})"
|
||||
self.assertEqual(lines[0], f"**Start with** {link}. Keep it zipped. "
|
||||
"mame0, mame1 and mame2 read it.")
|
||||
self.assertIn("Geolith reads `aes.zip` instead.", lines)
|
||||
self.assertIn("Geolith reads [`aes.zip`](#sha1-111111111111) instead.", lines)
|
||||
|
||||
def test_another_consoles_file_or_an_emulators_folder_is_left_out(self) -> None:
|
||||
text = "\n".join(gs._start_with([self.aes], self.consumers, section=("SNK", "Neo Geo")))
|
||||
@@ -164,7 +164,8 @@ class ArcadeSetsAreNamedWhereverFiled(unittest.TestCase):
|
||||
for i in range(8)]
|
||||
consumers = _consumers({f["sha1"]: [("emu", {"required": True})] for f in files})
|
||||
self.assertEqual(gs._start_with(files, consumers, first_below=False)[0],
|
||||
"**Start with**: `rom0.bin`, `rom1.bin`, `rom2.bin` and 5 more files.")
|
||||
"**Start with**: [`rom0.bin`](#sha1-aaaaaaaaaaaa), [`rom1.bin`](#sha1-bbbbbbbbbbbb), "
|
||||
"[`rom2.bin`](#sha1-cccccccccccc) and 5 more files.")
|
||||
|
||||
|
||||
class CardsSayWhichReleaseTheSetMatches(unittest.TestCase):
|
||||
|
||||
@@ -29,6 +29,11 @@ DIST = REPO_ROOT / "website" / "dist"
|
||||
CONTENT = REPO_ROOT / "website" / "src" / "content" / "docs"
|
||||
|
||||
|
||||
def _unlinked(text: str) -> str:
|
||||
"""A start line's wording: each file it names links to its card."""
|
||||
return re.sub(r"\[(`[^`]*`)\]\([^)]*\)", r"\1", text)
|
||||
|
||||
|
||||
def _file(name: str, char: str, path: str = "") -> dict:
|
||||
return {"name": name, "sha1": char * 40, "md5": char * 32, "size": 4096,
|
||||
"path": path or f"bios/Nintendo/Game Boy/{name}"}
|
||||
@@ -73,7 +78,7 @@ class AFileFewRequireIsNoStartFile(unittest.TestCase):
|
||||
consumers = _consumers({"1" * 40: [
|
||||
("emux_gb", {"name": "dmg_boot.bin", "required": True}),
|
||||
("gambatte", optional), ("mgba", optional), ("sameboy", optional)]})
|
||||
line = gs._start_with([boot], consumers)[0]
|
||||
line = _unlinked(gs._start_with([boot], consumers)[0])
|
||||
self.assertTrue(line.startswith("**No file needed** with "), line)
|
||||
self.assertRegex(line, r"Only \S.* requires `dmg_boot.bin`\.$")
|
||||
|
||||
@@ -82,7 +87,7 @@ class AFileFewRequireIsNoStartFile(unittest.TestCase):
|
||||
required = {"name": "scph5501.bin", "required": True}
|
||||
consumers = _consumers({"2" * 40: [
|
||||
("a", required), ("b", required), ("c", {"required": False, "hle_fallback": True})]})
|
||||
self.assertEqual(gs._start_with([bios], consumers)[0], "**Start with**: `scph5501.bin`.")
|
||||
self.assertEqual(_unlinked(gs._start_with([bios], consumers)[0]), "**Start with**: `scph5501.bin`.")
|
||||
|
||||
def test_an_entry_read_for_a_system_with_no_section_is_dropped(self) -> None:
|
||||
sample = _file("ym2608_adpcm_rom.bin", "3", "bios/Nintendo/NES/ym2608_adpcm_rom.bin")
|
||||
@@ -131,7 +136,7 @@ class TheNeoGeoOpensOnItsSet(unittest.TestCase):
|
||||
"5" * 40: [("ares", {"name": "neo-epo.sp1", "required": True,
|
||||
"system": "snk-neogeo"})],
|
||||
})
|
||||
lines = gs._start_with([neogeo, epo], consumers, section=("SNK", "Neo Geo"))
|
||||
lines = [_unlinked(line) for line in gs._start_with([neogeo, epo], consumers, section=("SNK", "Neo Geo"))]
|
||||
self.assertTrue(lines[0].startswith("**Start with** `neogeo.zip`. Keep it zipped. "))
|
||||
self.assertIn("ares reads `neo-epo.sp1` instead.", lines)
|
||||
cards = gs._console_cards("SNK", [epo, neogeo], consumers, set(),
|
||||
|
||||
@@ -52,11 +52,17 @@ def _consumers(reads: dict[str, list[tuple[str, dict]]]) -> types.SimpleNamespac
|
||||
)
|
||||
|
||||
|
||||
def _unlinked(text: str) -> str:
|
||||
"""The wording of a start line: each file it names links to its card,
|
||||
which EachFileDownloadsFromItsCard holds."""
|
||||
return re.sub(r"\[(`[^`]*`)\]\([^)]*\)", r"\1", text)
|
||||
|
||||
|
||||
def _page(stem: str) -> str:
|
||||
path = SYSTEMS / f"{stem}.md"
|
||||
if not path.is_file():
|
||||
raise unittest.SkipTest(f"systems/{stem}.md not generated")
|
||||
return path.read_text(encoding="utf-8")
|
||||
return _unlinked(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def _start_line(text: str) -> str:
|
||||
@@ -83,7 +89,7 @@ class AStartFileIsNoOnesExtra(unittest.TestCase):
|
||||
"1" * 40: [(e, {"name": "naomi.zip", "required": True}) for e, _ in both + extra],
|
||||
"2" * 40: both + extra,
|
||||
})
|
||||
self.assertEqual(gs._start_with([naomi, hod2], consumers),
|
||||
self.assertEqual([_unlinked(line) for line in gs._start_with([naomi, hod2], consumers)],
|
||||
["**Start with**: `naomi.zip`.", ""])
|
||||
|
||||
def test_the_naomi_page_opens_on_naomi_zip(self) -> None:
|
||||
@@ -95,7 +101,7 @@ class AStartFileIsNoOnesExtra(unittest.TestCase):
|
||||
"3" * 40: [(e, {"name": "bios7.bin", "required": True}) for e in ("a", "b")],
|
||||
"4" * 40: [(e, {"name": "bios9.bin", "required": True}) for e in ("a", "b")],
|
||||
})
|
||||
self.assertEqual(gs._start_with([bios7, bios9], consumers)[0],
|
||||
self.assertEqual(_unlinked(gs._start_with([bios7, bios9], consumers)[0]),
|
||||
"**Start with**: `bios7.bin`, `bios9.bin`.")
|
||||
|
||||
def test_some_files_each_is_not_every_file_for_one(self) -> None:
|
||||
@@ -121,7 +127,7 @@ class OtherNamesForOneSlotReadAsAlternatives(unittest.TestCase):
|
||||
for e in ("mesen", "nestopia", "fceumm")],
|
||||
"6" * 40: [("fbneo", {"name": "fdsbios.zip", "required": True})],
|
||||
})
|
||||
self.assertEqual(gs._start_with([disk, fds], consumers)[0],
|
||||
self.assertEqual(_unlinked(gs._start_with([disk, fds], consumers)[0]),
|
||||
"**Start with**: `disksys.rom`. FinalBurn Neo reads `fdsbios.zip` instead.")
|
||||
|
||||
def test_a_card_read_under_another_name_says_both(self) -> None:
|
||||
@@ -131,7 +137,7 @@ class OtherNamesForOneSlotReadAsAlternatives(unittest.TestCase):
|
||||
"7" * 40: [("c", {"name": "short.rom", "required": True})],
|
||||
})
|
||||
self.assertIn("reads `long name (1986).bin` (as `short.rom`) instead.",
|
||||
gs._start_with([disk, card], consumers)[0])
|
||||
_unlinked(gs._start_with([disk, card], consumers)[0]))
|
||||
|
||||
def test_a_regional_line_names_one_file_per_reader(self) -> None:
|
||||
mega_cd = _page("sega").split("{ #mega-cd }", 1)[1].split("\n## ", 1)[0]
|
||||
@@ -293,5 +299,44 @@ class OneLabelOnceOnABoardPage(unittest.TestCase):
|
||||
[])
|
||||
|
||||
|
||||
class EachFileDownloadsFromItsCard(unittest.TestCase):
|
||||
"""The Apple II page named eight start files and gave no visible way to
|
||||
get one: the download was a "(download)" after the collection path,
|
||||
fourth of a card's facts."""
|
||||
|
||||
def test_the_card_title_holds_the_download(self) -> None:
|
||||
from cross_reference import FileConsumers # noqa: PLC0415
|
||||
|
||||
f = _file("apple2-asoft-auto.rom", "3", "bios/Apple/Apple II/apple2-asoft-auto.rom")
|
||||
title = next(line for line in gs._system_file_card(f, FileConsumers({"files": {}}))
|
||||
if "rb-file-title" in line)
|
||||
self.assertIn('class="rb-download" href="https://raw.githubusercontent.com/Abdess/'
|
||||
'retrobios/main/bios/Apple/Apple%20II/apple2-asoft-auto.rom"', title)
|
||||
self.assertIn('aria-label="Download apple2-asoft-auto.rom"', title)
|
||||
facts = "\n".join(gs._system_file_card(f, FileConsumers({"files": {}})))
|
||||
self.assertNotIn("(download)", facts)
|
||||
|
||||
def test_a_start_name_leads_to_its_card(self) -> None:
|
||||
line = gs._link_held_names("- **Apple II**: `a.rom`, `b.rom`.",
|
||||
{"a.rom": "a" * 40, "b.rom": "b" * 40}, {"a" * 40})
|
||||
self.assertEqual(line, "- **Apple II**: [`a.rom`](#sha1-aaaaaaaaaaaa), `b.rom`.")
|
||||
|
||||
def test_a_card_on_another_part_is_reached_by_its_permalink(self) -> None:
|
||||
full = next(iter(json.loads((REPO_ROOT / "database.json").read_text())["files"]))
|
||||
here = f'[`x.rom`](#sha1-{full[:12]})'
|
||||
away = gs._cards_elsewhere(here)
|
||||
self.assertEqual(away, f"[`x.rom`](/retrobios/files/?sha1={full})")
|
||||
held = here + f'\n<h3 id="sha1-{full[:12]}" class="rb-file-title">'
|
||||
self.assertEqual(gs._cards_elsewhere(held), held)
|
||||
|
||||
def test_every_start_name_on_the_built_apple_page_links(self) -> None:
|
||||
page = next(SYSTEMS.glob("apple.md*"), None)
|
||||
if page is None:
|
||||
self.skipTest("site not generated")
|
||||
section = page.read_text(encoding="utf-8").split("## Apple II {", 1)[1]
|
||||
start = section.split("<section", 1)[0]
|
||||
self.assertIn("[`apple2-asoft-auto.rom`](#sha1-33a24f5489ba)", start)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -99,7 +99,7 @@
|
||||
}
|
||||
|
||||
/* Links and focus */
|
||||
.sl-markdown-content a:not(.rb-button):not(.rb-badge):not(.rb-permalink) {
|
||||
.sl-markdown-content a:not(.rb-button):not(.rb-badge):not(.rb-permalink):not(.rb-download) {
|
||||
color: var(--rb-link);
|
||||
text-decoration-thickness: 0.08em;
|
||||
text-underline-offset: 0.14em;
|
||||
@@ -486,6 +486,22 @@ body:has(.rb-sys-file, .rb-file-entry) { --sl-content-width: var(--rb-grid-width
|
||||
.rb-permalink { opacity: 1; }
|
||||
}
|
||||
|
||||
.rb-download {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 32px;
|
||||
margin-left: 0.5rem;
|
||||
padding: 0.15rem 0.7rem;
|
||||
border: 1px solid var(--rb-link);
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: var(--rb-link);
|
||||
text-decoration: none;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.rb-download:hover { background: var(--sl-color-accent-low); }
|
||||
|
||||
/* The facts of a file card: label column, value column, one column on a phone. */
|
||||
.sl-markdown-content dl.rb-facts {
|
||||
display: grid;
|
||||
|
||||
Reference in new issue
Block a user