From 506d18c935304237c5af7f5348838e767cf6c108 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 23 Oct 2026 15:57:24 +0200 Subject: [PATCH] fix: read a spaced line list in prose citations --- scripts/sourceref.py | 17 +++++++++++++++++ tests/test_profile_sync.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/scripts/sourceref.py b/scripts/sourceref.py index 33589874..6bb26a21 100644 --- a/scripts/sourceref.py +++ b/scripts/sourceref.py @@ -231,6 +231,14 @@ PROSE_CONT_RE = re.compile(r",(?P\d+(?:-\d+)?)(?![\w-])(?!\.\d)") # A spaced continuation is accepted only for a range: `x.c:55-71, 80-147` # follows the source_ref convention, while `x.c:100, 200 files` is prose. PROSE_CONT_SPACED_RE = re.compile(r", +(?P\d+-\d+)(?![\w-])(?!\.\d)") +# A spaced single line joins the run only inside a list that closes the +# citation: `bgmusic.c:29, 321)` and `.gitlab-ci.yml:110, 116, 139, 400` +# end on a bracket, a semicolon, a sentence end or the line end, while in +# `x.c:100, 200 files` a word follows and the number is prose. +PROSE_CONT_LIST_RE = re.compile( + r"(?:, +\d+(?:-\d+)?(?![\w-])(?!\.\d))+(?=[)\];]|\.(?!\w)|\s*$)" +) +_PROSE_LIST_RANGE_RE = re.compile(r"\d+(?:-\d+)?") # Words that read as prose before a filename. Anything else in project # position marks the citation external, the way `munt ROMInfo.cpp` does in # a source_ref: the profile does not declare that repository. @@ -391,6 +399,15 @@ def _prose_runs( end = match.end() while True: cont = PROSE_CONT_RE.match(line, end) + if cont is None and (listed := PROSE_CONT_LIST_RE.match(line, end)): + spans.extend( + (item.start(), item.end()) + for item in _PROSE_LIST_RANGE_RE.finditer( + line, end, listed.end() + ) + ) + end = listed.end() + break if cont is None: cont = PROSE_CONT_SPACED_RE.match(line, end) if cont is None: diff --git a/tests/test_profile_sync.py b/tests/test_profile_sync.py index 9eb3eec0..5ffe260c 100644 --- a/tests/test_profile_sync.py +++ b/tests/test_profile_sync.py @@ -3599,6 +3599,37 @@ class TestCollectCitations(unittest.TestCase): citation = collect_citations(document)[0] self.assertEqual(citation.ref, "x.c:100") + def test_spaced_lone_number_closing_the_citation_joins_the_run(self): + # vkquake wrote `(Quake/bgmusic.c:29, 321).` and cemu a list of ten + # lines of .gitlab-ci.yml: only the first number was ever compared. + cases = { + "music (Quake/bgmusic.c:29, 321).\n": [(29, 29), (321, 321)], + "(ref: a/b.cpp:1586-1633, 2646).\n": [(1586, 1633), (2646, 2646)], + "jobs in .gitlab-ci.yml:110, 116, 139-140\n": [ + (110, 110), (116, 116), (139, 140) + ], + "read at x.c:5, 6; then y\n": [(5, 5), (6, 6)], + } + for text, spans in cases.items(): + citation = collect_citations({"notes": text})[0] + self.assertEqual( + [(p.start, p.end) for p in citation.parts], spans, text + ) + line = text.splitlines()[0] + self.assertEqual( + [line[lo:hi] for lo, hi in citation.spans], + [p.raw for p in citation.parts], + ) + + def test_spaced_number_list_followed_by_a_word_stays_prose(self): + for text in ( + "It reads x.c:5, 6, 7 players at once.\n", + "It reads x.c:5, 6 and 7 at once.\n", + "Lines 3, 4 and 5, version 2, 3 players.\n", + ): + refs = [c.ref for c in collect_citations({"notes": text})] + self.assertEqual(refs, ["x.c:5"] if "x.c" in text else [], text) + def test_declared_repository_name_is_not_external(self): document = { "upstream": "https://github.com/o/mednafen",