From cf4afccf047c0b10fccfe2ffabc7662b45a6a3d0 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 6 Nov 2026 15:10:40 +0100 Subject: [PATCH] fix: show source_ref annotations on the cards --- scripts/sitesources.py | 23 +++++++++++++++++----- scripts/sourceref.py | 19 ++++++++++++++++++ tests/test_site_render.py | 41 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 5 deletions(-) diff --git a/scripts/sitesources.py b/scripts/sitesources.py index c28b7c5f1..a5cdbbccc 100644 --- a/scripts/sitesources.py +++ b/scripts/sitesources.py @@ -16,9 +16,10 @@ from dataclasses import dataclass from pathlib import Path import upstream -from siterender import _by_mode +from siterender import _by_mode, _escape_tags from sourceref import ( - _HEX_ADDRESS_RE, _prose_runs, is_binary_citation, ref_role, source_ref_values, + _HEX_ADDRESS_RE, _prose_runs, is_binary_citation, ref_annotation, ref_role, + source_ref_values, split_source_ref, ) @@ -512,7 +513,7 @@ def source_ref_items(profile: dict, value: str | dict | None) -> list[tuple[str, for part in split_source_ref(refs): if is_binary_citation(part.path): text, binary = _binary_citation(part.path, binary) - rendered.append((text, "")) + rendered.append((text + _annotation_markdown(ref_annotation(part.raw)), "")) continue binary = "" role, bare = ref_role(part.path) @@ -544,19 +545,31 @@ def source_ref_items(profile: dict, value: str | dict | None) -> list[tuple[str, role = (role_label(profile, current_role, part.path) if current_role and _two_repositories(profile) else "") + note = _annotation_markdown(ref_annotation(part.raw)) if selected is None: - rendered.append((f"`{display}`", role)) + rendered.append((f"`{display}`{note}", role)) else: repo, pin = selected url = _source_permalink( repo, pin, real_path, part.start, part.end ) - rendered.append((f"[`{display}`]({url})", role)) + rendered.append((f"[`{display}`]({url}){note}", role)) groups.append((label, rendered or [(f"`{refs}`", "")])) return groups +def _annotation_markdown(text: str) -> str: + """A part's annotation after its citation, as written: `(lsf default)`. + + Profile prose, so placeholders keep their brackets and nothing in it + opens emphasis or a table cell. + """ + if not text: + return "" + return " " + _escape_tags(re.sub(r"([*|\\])", r"\\\1", text)) + + def _binary_citation(path: str, previous: str) -> tuple[str, str]: """A part citing code inside a shipped binary, and the binary it names. diff --git a/scripts/sourceref.py b/scripts/sourceref.py index ec980b6e2..a7436f977 100644 --- a/scripts/sourceref.py +++ b/scripts/sourceref.py @@ -123,6 +123,25 @@ def split_source_ref(ref: str) -> list[RefPart]: return parts +_LOCATION_RE = re.compile(r":\d+(?:-\d+)?|^\s*\d+(?:-\d+)?(?=\s|$)") + + +def ref_annotation(raw: str) -> str: + """The words a part writes beside its location, for a reader. + + split_source_ref drops them because a comparison has no use for them, + but `src/loader2.cpp:284 (lsf default), src/netcfg.cpp:204 (lsf + override)` says which line wins only through them, and 1,103 of the + profiles' annotations were on no page. + """ + paren = raw.find("(") + head = raw if paren < 0 else raw[:paren] + located = _LOCATION_RE.search(head) + if located: + return raw[located.end():].strip() + return raw[paren:].strip() if paren >= 0 else "" + + def ref_prose_text(ref: str) -> str: """The text of a source_ref its parts do not read, positions kept. diff --git a/tests/test_site_render.py b/tests/test_site_render.py index 9628bd5ed..7fe9c52dc 100644 --- a/tests/test_site_render.py +++ b/tests/test_site_render.py @@ -255,6 +255,47 @@ class AnAliasTreeIsListedOnItsParentsPage(unittest.TestCase): self.assertEqual([t["ref"] for t in trees], ["freeintv-overlays"]) +class CitationsKeepTheirAnnotations(unittest.TestCase): + """split_source_ref drops annotations for profile_sync, and the cards + reused it: abuse's two lsf lines lost "(lsf default)" and "(lsf + override)", the only words saying which one wins.""" + + @mock.patch("sitesources.tree_holds", lambda *_args: True) + def test_each_part_shows_what_the_profile_wrote_beside_it(self): + import sitesources + + profile = {"source": "https://github.com/Xenoveritas/abuse", + "source_commit": "a" * 40} + ref = ("src/loader2.cpp:284 (lsf default), src/netcfg.cpp:204 (lsf override), " + "libretro.cpp:1520-1521 candidates_a1200, 30-31 (see ), src/x.c:1") + (label, items), = sitesources.source_ref_items(profile, ref) + texts = [text for text, _role in items] + self.assertTrue(texts[0].endswith(") (lsf default)"), texts[0]) + self.assertTrue(texts[1].endswith(") (lsf override)"), texts[1]) + self.assertTrue(texts[2].endswith(") candidates_a1200"), texts[2]) + self.assertTrue(texts[3].endswith(") (see <system_dir>)"), texts[3]) + self.assertTrue(texts[4].endswith(")"), texts[4]) + + def test_the_annotation_is_what_follows_the_location(self): + from sourceref import ref_annotation + + self.assertEqual(ref_annotation("munt ROMInfo.cpp:206-213"), "") + self.assertEqual(ref_annotation("source atari800/src/sysrom.c:91"), "") + self.assertEqual(ref_annotation("doc/notes.txt (read at boot)"), "(read at boot)") + self.assertEqual(ref_annotation("a.c (see b.c:12)"), "(see b.c:12)") + self.assertEqual(ref_annotation("0x53c338-0x53c36f (dreamBios row)"), "(dreamBios row)") + + def test_a_binary_citation_keeps_its_annotation(self): + import sitesources + + (_label, items), = sitesources.source_ref_items( + {}, "demul.exe 0x6b4d80-0x6b4ea3 (dc table), 0x53ca4d-0x53ca7e (load, fatal)") + self.assertEqual([text for text, _ in items], [ + "offset `0x6b4d80-0x6b4ea3` in `demul.exe` (dc table)", + "offset `0x53ca4d-0x53ca7e` in `demul.exe` (load, fatal)", + ]) + + class ForgesUpstreamDoesNotRead(unittest.TestCase): """Profiles pinned on SourceForge, voidpoint.io, Bitbucket or Software Heritage showed no revision, and their citations were plain code."""