feat: link and list every file on site pages

This commit is contained in:
Abdessamad Derraz committed 2026-10-17 23:51:02 +02:00
1 parent bd6617cc6a
commit 43af290102
3 files changed
+89 -18

No files matched your search

+22
View File
@@ -684,3 +684,25 @@
animation-iteration-count: 1 !important;
}
}
/* A file card can be cited by URL: its link shows on hover, and the card a
link lands on is marked. */
.rb-permalink {
margin-left: 0.4rem;
color: var(--md-default-fg-color--light, var(--rb-border));
text-decoration: none;
opacity: 0;
transition: opacity 0.15s;
}
.rb-file-entry:hover .rb-permalink,
.rb-sys-file:hover .rb-permalink,
.rb-permalink:focus {
opacity: 1;
}
.rb-file-entry:target,
.rb-sys-file:target {
outline: 2px solid var(--md-accent-fg-color, var(--rb-border));
outline-offset: 2px;
scroll-margin-top: 4rem;
}
+42 -18
View File
@@ -1776,8 +1776,11 @@ def _system_file_card(f: dict, consumers: FileConsumers) -> list[str]:
sha1_short = sha1_full[:12] if sha1_full != "unknown" else "-"
md5_short = md5_full[:12] if md5_full != "unknown" else "-"
lines = ['<div class="rb-sys-file" markdown>', ""]
lines.append(f'**`{name}`** <span class="rb-badge rb-badge-muted">{size}</span>')
anchor = f"sha1-{sha1_full[:12]}" if sha1_full != "unknown" else ""
id_attr = f' id="{anchor}"' if anchor else ""
lines = [f'<div class="rb-sys-file"{id_attr} markdown>', ""]
permalink = f' <a class="rb-permalink" href="#{anchor}" title="Link to this file">#</a>' if anchor else ""
lines.append(f'**`{name}`** <span class="rb-badge rb-badge-muted">{size}</span>{permalink}')
lines.append("")
if "/.variants/" in f.get("path", ""):
lines.append(f"- Stored as: `{f['path'].rsplit('/', 1)[-1]}`")
@@ -2040,9 +2043,12 @@ def _render_emulator_file(
consumers: FileConsumers | None,
files: list,
_file_available,
anchor: str = "",
) -> list[str]:
"""Render one row of an emulator profile's file table.
`anchor` is the card's id on the page, so one file can be cited by URL.
Split out of generate_emulator_page, where it was a 228-line loop body
carrying most of that function's branching: thirty-odd optional fields,
each with its own badge, hash line or note.
@@ -2091,11 +2097,13 @@ def _render_emulator_file(
border_cls = (
"rb-file-entry-required" if required else "rb-file-entry-optional"
)
id_attr = f' id="{anchor}"' if anchor else ""
lines.append(
f'<div class="rb-file-entry {border_cls}" markdown>'
f'<div class="rb-file-entry {border_cls}"{id_attr} markdown>'
)
lines.append("")
lines.append(f"**`{fname}`** {badge_str}")
permalink = f' <a class="rb-permalink" href="#{anchor}" title="Link to this file">#</a>' if anchor else ""
lines.append(f"**`{fname}`** {badge_str}{permalink}")
if desc:
lines.append(f"<br>{_escape_tags(str(desc))}")
lines.append("")
@@ -2103,6 +2111,12 @@ def _render_emulator_file(
details = []
if fpath and fpath != fname:
details.append(f"Path: `{fpath}`")
standalone_path = f.get("standalone_path", "")
if standalone_path and standalone_path != fpath:
# The standalone build reads it elsewhere: MesenCE's dsp1.rom goes
# under Firmware/, and a page naming only the core's path sent its
# users to the wrong folder.
details.append(f"Standalone path: `{standalone_path}`")
if fsystem:
details.append(f"System: {_system_link(fsystem, '../')}")
if size:
@@ -2178,22 +2192,25 @@ def _render_emulator_file(
if fnote:
lines.append(f"- {_escape_tags(str(fnote))}")
if contents:
lines.append(f"- Contents ({len(contents)} entries):")
for c in contents[:10]:
# Every member with the hashes the emulator checks: MAME and FBNeo
# verify the members, never the archive, so a member's crc32 is the
# value a reader compares a set against.
lines.append(f"- Contents ({len(contents)} members):")
for c in contents:
if isinstance(c, dict):
cname = c.get("name", "")
cdesc = c.get("description", "")
csize = c.get("size", "")
parts = [f"`{cname}`"]
if cdesc:
parts.append(cdesc)
if csize:
parts.append(_fmt_size(csize))
lines.append(f" - {' -'.join(parts)}")
parts = []
if c.get("description"):
parts.append(_escape_tags(str(c["description"])))
if c.get("size"):
parts.append(_fmt_size(c["size"]))
if c.get("crc32"):
parts.append(f"CRC32 `{c['crc32']}`")
if c.get("sha1"):
parts.append(f"SHA1 `{c['sha1']}`")
extra = f": {', '.join(parts)}" if parts else ""
lines.append(f" - `{c.get('name', '')}`{extra}")
else:
lines.append(f" - {c}")
if len(contents) > 10:
lines.append(f" - ... and {len(contents) - 10} more")
lines.append("")
lines.append("</div>")
lines.append("")
@@ -2526,10 +2543,17 @@ def generate_emulator_page(
lines.extend(_emulator_file_summary(files, _file_available))
# File table
used: dict[str, int] = {}
for f in files:
base = "file-" + (
re.sub(r"[^a-z0-9]+", "-", str(f.get("path") or f.get("name", "")).lower())
.strip("-") or "entry"
)
used[base] = used.get(base, 0) + 1
anchor = base if used[base] == 1 else f"{base}-{used[base]}"
lines.extend(
_render_emulator_file(
f, profile, consumers, files, _file_available
f, profile, consumers, files, _file_available, anchor
)
)
+25
View File
@@ -3598,5 +3598,30 @@ class GeneratedArtifactsMeetTheirSchemas(unittest.TestCase):
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
class AFileCardSaysAllItKnows(unittest.TestCase):
"""Emulator pages cut a set at 10 members without their hashes, never
showed standalone_path, and gave no file an anchor to link to."""
def test_members_standalone_path_and_anchor(self):
from scripts.generate_site import _render_emulator_file
entry = {
"name": "neogeo.zip", "path": "neogeo.zip", "category": "bios_zip",
"standalone_path": "Firmware/neogeo.zip",
"contents": [{"name": f"m{i}.bin", "size": 1024, "crc32": f"{i:08x}"}
for i in range(12)],
}
text = "\n".join(_render_emulator_file(
entry, {"files": [entry]}, None, [entry], lambda f: True, "file-neogeo-zip",
))
self.assertIn('id="file-neogeo-zip"', text)
self.assertIn('href="#file-neogeo-zip"', text)
self.assertIn("Standalone path: `Firmware/neogeo.zip`", text)
self.assertIn("`m11.bin`", text)
self.assertIn("CRC32 `0000000b`", text)
self.assertNotIn("more", text)
if __name__ == "__main__":
unittest.main()