fix: address a hashed file card by its hash

An emulator card's anchor changed when a second entry with the same
path was added, so cited links landed at the page top. A card whose
entry declares a hash is now addressed by it, and the first card of a
path keeps the plain file-<path> id as an alias.
This commit is contained in:
Abdessamad Derraz committed 2026-10-21 10:12:26 +02:00
1 parent 73ff57468f
commit 2239df669f
2 files changed
+71 -11

No files matched your search

+46 -11
View File
@@ -1615,11 +1615,14 @@ def dataset_structured_data(exports: list[dict], db: dict) -> dict:
def emulator_file_anchors(files: list[dict]) -> list[str]:
"""The id of each file card on an emulator page, in profile order.
A path declared once keeps its plain anchor. One declared for several
versions (raze's nine DUKE3D.GRP) takes what tells the version apart:
its hash, else its size, else, when even the size is shared, its
description. The page and its JSON-LD both read this, so a structured
part links to its card.
An entry that declares a hash is addressed by it: its anchor depends on
its own content, never on the entries beside it, and a second version
added under the same path moves nothing. An entry without one keeps
its plain anchor when its path is declared once, and otherwise takes
its size, else, when even the size is shared, its description. The
page and its JSON-LD both read this, so a structured part links to its
card. The plain anchor of a hashed card stays reachable through
emulator_file_aliases.
"""
bases = [
"file-" + (
@@ -1638,12 +1641,15 @@ def emulator_file_anchors(files: list[dict]) -> list[str]:
return digest[:8]
return f"{f['size']}b" if f.get("size") else ""
keys = [(b, _version_key(f)) if b in repeated else (b, "") for f, b in zip(files, bases)]
keys = [
(b, _version_key(f)) if b in repeated or _first_hash_value(f) else (b, "")
for f, b in zip(files, bases)
]
used: dict[str, int] = {}
anchors = []
for f, (base, key) in zip(files, keys):
anchor = f"{base}-{key}" if key else base
if base in repeated and keys.count((base, key)) > 1 and f.get("description"):
if keys.count((base, key)) > 1 and f.get("description"):
described = re.sub(r"[^a-z0-9]+", "-", str(f["description"]).lower()).strip("-")
anchor = f"{anchor}-{described[:40].strip('-')}"
used[anchor] = used.get(anchor, 0) + 1
@@ -1653,6 +1659,30 @@ def emulator_file_anchors(files: list[dict]) -> list[str]:
return anchors
def emulator_file_aliases(files: list[dict], anchors: list[str]) -> list[str]:
"""The plain file-<path> id each card also answers to, or "".
A link cited as emulators/raze/#file-duke3d-grp must still land once the
card is addressed by its hash: the first card of that path keeps the
plain id, unless another card already is it.
"""
taken = set(anchors)
aliases = []
for f, anchor in zip(files, anchors):
base = "file-" + (
re.sub(
r"[^a-z0-9]+", "-",
str(f.get("path") or f.get("standalone_path") or f.get("name", "")).lower(),
).strip("-") or "entry"
)
if base in taken or anchor == base:
aliases.append("")
else:
taken.add(base)
aliases.append(base)
return aliases
def emulator_structured_data(profile: dict) -> dict:
"""What an emulator page states, for a reader of its JSON-LD.
@@ -3087,10 +3117,12 @@ def _render_emulator_file(
_file_available: Callable[[dict], bool],
anchor: str = "",
owner: str = "",
alias: 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.
`anchor` is the card's id on the page, so one file can be cited by URL,
and `alias` a second id it answers to.
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,
@@ -3128,7 +3160,8 @@ def _render_emulator_file(
badge_str = " ".join(badges)
lines.append('<div class="rb-file-entry" markdown>')
lines.append("")
lines.append(_card_title(f"{_code(fname)} {badge_str}", anchor))
marker = f'<span id="{alias}"></span>' if alias else ""
lines.append(_card_title(f"{marker}{_code(fname)} {badge_str}", anchor))
lines.append("")
if desc:
lines.extend([_escape_tags(str(desc)), ""])
@@ -3782,10 +3815,12 @@ def generate_emulator_page(
lines.extend(_emulator_file_summary(files, _file_available))
for f, anchor in zip(files, emulator_file_anchors(files)):
anchors = emulator_file_anchors(files)
for f, anchor, alias in zip(files, anchors, emulator_file_aliases(files, anchors)):
lines.extend(
_render_emulator_file(
f, profile, consumers, files, _file_available, anchor, name
f, profile, consumers, files, _file_available, anchor, name,
alias,
)
)
+25
View File
@@ -69,6 +69,31 @@ class WhoShipsTheBuildAndWhy(unittest.TestCase):
"found only as rebuilt copies) |", page)
class ACardKeepsItsAddress(unittest.TestCase):
"""emulators/raze/#file-duke3d-grp stopped pointing at its card once a
second version of the path was declared: the anchor of the first one
became file-duke3d-grp-<hash>."""
def test_a_second_version_moves_nothing(self):
one = {"name": "DUKE3D.GRP", "md5": "aaaa1111bbbb"}
two = {"name": "DUKE3D.GRP", "md5": "cccc2222dddd"}
before = gs.emulator_file_anchors([one])
after = gs.emulator_file_anchors([two, one])
self.assertEqual(before, ["file-duke3d-grp-aaaa1111"])
self.assertEqual(after[1], before[0])
def test_the_plain_address_still_lands(self):
files = [{"name": "DUKE3D.GRP", "md5": "aaaa1111bbbb"}]
page = _page("raze", {"type": "standalone", "files": files})
self.assertIn('<span id="file-duke3d-grp"></span>', page)
self.assertIn("{ #file-duke3d-grp-aaaa1111 .rb-file-title }", page)
# A card already holding the plain id gives it to no one else.
files = [{"name": "x.bin"}, {"name": "x.bin", "sha1": "ab" * 20}]
anchors = gs.emulator_file_anchors(files)
self.assertEqual(anchors, ["file-x-bin", "file-x-bin-abababab"])
self.assertEqual(gs.emulator_file_aliases(files, anchors), ["", ""])
class WhatASearchSnippetSays(unittest.TestCase):
def test_a_core_loading_no_file_promises_no_hash(self):
"""152 pages of cores that load no file announced paths and hashes."""