Coverage for src/software_citation_sync/readme.py: 94%
33 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-06-27 17:58 +0000
« prev ^ index » next coverage.py v7.14.3, created at 2026-06-27 17:58 +0000
1# SPDX-FileCopyrightText: 2026 Arcangelo Massari <github@a.arcangelomassari.com>
2#
3# SPDX-License-Identifier: ISC
5from __future__ import annotations
7import re
8from pathlib import Path
10START_MARKER = "<!-- software-citation-action:start -->"
11END_MARKER = "<!-- software-citation-action:end -->"
12BIBTEX_REFERENCE_PATTERN = re.compile(r"[^0-9A-Za-z_.:-]+")
15def bibtex_reference(title: str, version: str) -> str:
16 return BIBTEX_REFERENCE_PATTERN.sub("-", f"{title}-{version}").strip("-")
19def expected_readme_block(version: str, bibtex: str) -> str:
20 return "\n".join(
21 [
22 START_MARKER,
23 f"To cite the latest version of this software ({version}), use this BibTeX entry:",
24 "",
25 "```bibtex",
26 bibtex,
27 "```",
28 END_MARKER,
29 ],
30 )
33def readme_block_matches(path: Path, version: str, bibtex: str) -> bool:
34 content = path.read_text(encoding="utf-8")
35 return _current_block(content) == expected_readme_block(version, bibtex)
38def write_readme_block(path: Path, version: str, bibtex: str) -> bool:
39 content = path.read_text(encoding="utf-8")
40 block = expected_readme_block(version, bibtex)
41 current_block = _current_block(content)
42 if current_block == block:
43 return False
44 if current_block is None:
45 new_content = content.rstrip() + "\n\n" + block + "\n"
46 else:
47 new_content = content.replace(current_block, block)
48 path.write_text(new_content, encoding="utf-8")
49 return True
52def _current_block(content: str) -> str | None:
53 start = content.find(START_MARKER)
54 end = content.find(END_MARKER)
55 if start == -1 and end == -1:
56 return None
57 if start == -1 or end == -1 or end < start:
58 msg = "README citation block markers are incomplete or out of order"
59 raise ValueError(msg)
60 return content[start : end + len(END_MARKER)]