yams/tests/scripts/check_repository_metadata.py
trvon 8a72da96ff
test(metadata): align legacy symbol test cases with schema v39
- Use v39 metadata DB template for legacy symbol metadata test cases in
  metadata_schema_catch2_test.cpp and kg_store_catch2_test.cpp to avoid
  running against schema v40 where symbol tables are dropped.
- Use yams::test::make_temp_sqlite_path instead of raw getenv in
  metadata_schema_catch2_test.cpp and prune the stale portability allowlist entry.
- Add site directory to SKIP_PARTS in check_repository_metadata.py to prevent
  local docs builds from triggering retired marker warnings.

Signed-off-by: trvon <me@trevon.dev>
2026-09-22 09:37:00 -06:00

112 lines
3.7 KiB
Python
Executable file

#!/usr/bin/env python3
"""Validate canonical and mirrored repository metadata."""
from __future__ import annotations
import argparse
import os
from pathlib import Path
FORGEJO_URL = "https://git.trevon.dev/trevon/yams"
GITHUB_URL = "https://github.com/trvon/yams"
SOURCEHUT_MARKERS = ("https://sr.ht/~trvon/yams", "https://git.sr.ht/~trevon/yams")
EXPECTED_TEXT = {
"README.md": (GITHUB_URL, FORGEJO_URL),
"docs/index.md": (GITHUB_URL, FORGEJO_URL),
"CITATION.cff": (
f'repository-code: "{GITHUB_URL}"',
f'repository-artifact: "{FORGEJO_URL}"',
),
"mkdocs.yml": (
f"repo_url: {FORGEJO_URL}",
f"link: {GITHUB_URL}",
f"link: {FORGEJO_URL}",
),
"scripts/build-deb.sh": (
f"Homepage: {FORGEJO_URL}",
f"URL: {FORGEJO_URL}",
),
}
# These references describe historical releases or the still-active newsletter list.
SOURCEHUT_ALLOWLIST = {
"CHANGELOG.md",
"docs/newsletter.md",
"tests/scripts/check_repository_metadata.py",
}
SKIP_PARTS = {".git", "build", "builddir", "node_modules", "site", "subprojects", "third_party"}
RETIRED_PATHS = (".build.yml", "scripts/srht-collect-artifacts.sh")
def repository_files(root: Path):
# Prune before descent: filtering rglob results still traverses build/cache trees.
for directory, directories, filenames in os.walk(root, followlinks=False):
directories[:] = sorted(name for name in directories if name not in SKIP_PARTS)
for name in sorted(filenames):
path = Path(directory) / name
if name not in SKIP_PARTS and path.is_file():
yield path
def find_text_markers(path: Path, markers: tuple[str, ...]) -> set[str]:
"""Scan strict UTF-8 with bounded memory, including cross-chunk matches.
Consume the whole file even after finding every marker: invalid UTF-8 near
EOF must still reject the file exactly as the former read_text did.
"""
found: set[str] = set()
overlap = max((len(marker) for marker in markers), default=1) - 1
tail = ""
with path.open("r", encoding="utf-8") as stream:
while chunk := stream.read(65536):
text = tail + chunk
found.update(marker for marker in markers if marker in text)
tail = text[-overlap:] if overlap else ""
return found
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--root", type=Path, default=Path.cwd())
args = parser.parse_args()
root = args.root.resolve()
failures: list[str] = []
for relative, required in EXPECTED_TEXT.items():
path = root / relative
if not path.is_file():
failures.append(f"missing metadata file: {relative}")
continue
found = find_text_markers(path, required)
for value in required:
if value not in found:
failures.append(f"{relative}: missing {value!r}")
for relative in RETIRED_PATHS:
if (root / relative).exists():
failures.append(f"retired SourceHut path remains: {relative}")
for path in repository_files(root):
relative = path.relative_to(root).as_posix()
if relative in SOURCEHUT_ALLOWLIST:
continue
try:
found = find_text_markers(path, SOURCEHUT_MARKERS)
except UnicodeDecodeError:
continue
for marker in SOURCEHUT_MARKERS:
if marker in found:
failures.append(f"{relative}: retired repository URL remains: {marker}")
if failures:
for failure in failures:
print(failure)
return 1
print("repository metadata policy: clean")
return 0
if __name__ == "__main__":
raise SystemExit(main())