feat: prose citations link to the pinned source

Notes, file notes and platform details carried file:line citations as plain text although profile_sync keeps each on the profile's pin. A citation now links when the profile reads one repository and the path is rooted or already used by a structured source_ref. Platform detail source_refs go through the same renderer as file cards.
This commit is contained in:
Abdessamad Derraz committed 2026-10-20 05:44:08 +02:00
1 parent c87610b921
commit 10a1f475b0
3 files changed
+94 -5

No files matched your search

+14 -4
View File
@@ -2793,7 +2793,7 @@ def _render_emulator_file(
for d in details:
lines.append(f"- {d}")
if fnote:
lines.append(f"- {_escape_tags(str(fnote))}")
lines.append(f"- {_escape_tags(link_prose_citations(profile, str(fnote)))}")
if not in_repo and f.get("unsourceable"):
# The badge alone sent readers after a dump that cannot exist, and the
# reason was only on the gaps page, which the card did not link.
@@ -2876,14 +2876,23 @@ def _render_platform_details(profile: dict) -> list[str]:
if isinstance(pv, dict):
lines.append(f" **{pk}:**")
for sk, sv in pv.items():
lines.append(f" - {sk}: {_plain_value(sv)}")
lines.append(f" - {sk}: {_detail_value(profile, sk, sv)}")
else:
lines.append(f" **{pk}:** {_plain_value(pv)}")
lines.append(f" **{pk}:** {_detail_value(profile, pk, pv)}")
lines.append("")
return lines
def _detail_value(profile: dict, key: object, value: object) -> str:
"""A platform detail, its citations linked at the profile's pin."""
if key == "source_ref" and isinstance(value, (str, dict)):
return _source_ref_markdown(profile, value)
if isinstance(value, str):
return _escape_tags(link_prose_citations(profile, value))
return _plain_value(value)
def _plain_value(value: object) -> str:
"""A scalar or a list of scalars as a reader writes it, not as Python does."""
if isinstance(value, bool):
@@ -3176,7 +3185,7 @@ def generate_emulator_page(
# Notes
if notes:
indented = _admonition_body(notes)
indented = _admonition_body(link_prose_citations(profile, notes))
lines.extend(['???+ note "Technical notes"', f" {indented}", ""])
if not files:
@@ -4859,6 +4868,7 @@ from sitewrite import ( # noqa: E402,F401
_sweep_generated,
)
from sitesources import ( # noqa: E402,F401
link_prose_citations,
revision_markdown,
_repo_pins,
_forge_sources,
+54 -1
View File
@@ -162,4 +162,57 @@ def _source_ref_markdown(profile: dict, value: str | dict | None) -> str:
if label:
text = f"**{label}:** {text}"
rendered_groups.append(text)
return "; ".join(rendered_groups)
return ", ".join(rendered_groups)
def link_prose_citations(profile: dict, text: str) -> str:
"""Prose file:line citations as pinned links, where the target is certain.
profile_sync keeps these citations on their pin, and the page printed
4,328 of them as plain text. A citation links only when the profile reads
one repository and the path is rooted (it holds a directory) or is one a
structured source_ref of the profile already uses: a bare name could be
anywhere in the tree, and a link to the wrong file is a false citation.
Text already in backticks or in a link is left alone.
"""
from profile_sync import _prose_runs
candidates = _forge_sources(profile)
if len(candidates) != 1 or ":" not in text:
return text
repo, pin = candidates[0]
structured = {
part.path
for entry in profile.get("files") or []
if isinstance(entry, dict)
for _label, refs in source_ref_values(entry.get("source_ref"))
for part in split_source_ref(refs)
}
out = []
for line in text.split("\n"):
guarded = _guarded_spans(line)
edits = []
for _n, path_span, spans, parts, _run, wrapped in _prose_runs(line):
path = parts[0].path
if wrapped or " " in path or not _PATH_RE.fullmatch(path):
continue
if "/" not in path and path not in structured:
continue
if any(lo <= path_span[0] < hi for lo, hi in guarded):
continue
for index, ((lo, hi), part) in enumerate(zip(spans, parts)):
start = path_span[0] if index == 0 else lo
url = _source_permalink(repo, pin, part.path, part.start, part.end)
edits.append((start, hi, f"[`{line[start:hi]}`]({url})"))
for start, end, link in sorted(edits, reverse=True):
line = line[:start] + link + line[end:]
out.append(line)
return "\n".join(out)
_PATH_RE = re.compile(r"^[A-Za-z0-9_.@/+~-]+$")
def _guarded_spans(line: str) -> list[tuple[int, int]]:
"""Code spans and links of a markdown line."""
return [m.span() for m in re.finditer(r"`[^`]*`|\[[^\]]*\]\([^)]*\)", line)]
+26
View File
@@ -702,5 +702,31 @@ class ACardNamesThePacksThatInstallIt(unittest.TestCase):
self.assertEqual(_installed_links("a" * 40, consumers, {"retrobat"}), [])
class ProseCitationsLinkToThePin(unittest.TestCase):
"""4,328 file:line citations in notes and platform details were plain
text, while profile_sync keeps each one on the profile's pin."""
PROFILE = {"source": "https://github.com/libretro/FreeChaF",
"source_commit": "76c7a84f1f7e80f3e6f2bba96fe100cb24e99124",
"files": [{"name": "x", "source_ref": "libretro.c:10"}]}
def test_a_rooted_or_structured_path_is_linked(self):
from sitesources import link_prose_citations
text = link_prose_citations(
self.PROFILE, "BIOS loading is in src/libretro.c:186-208 and libretro.c:12.")
self.assertIn("[`src/libretro.c:186-208`](https://github.com/libretro/FreeChaF/blob/"
"76c7a84f1f7e80f3e6f2bba96fe100cb24e99124/src/libretro.c#L186-L208)", text)
self.assertIn("[`libretro.c:12`](", text)
def test_what_cannot_be_placed_stays_text(self):
from sitesources import link_prose_citations
self.assertEqual(link_prose_citations(self.PROFILE, "see memory.c:5"), "see memory.c:5")
self.assertEqual(link_prose_citations(self.PROFILE, "in `src/a.c:5`"), "in `src/a.c:5`")
two = {**self.PROFILE, "upstream": "https://github.com/x/y", "upstream_commit": "a" * 40}
self.assertEqual(link_prose_citations(two, "src/a.c:5"), "src/a.c:5")
if __name__ == "__main__":
unittest.main()