validate_catalog.py 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. #!/usr/bin/env python3
  2. """Validate README package inventory and headline counts."""
  3. from __future__ import annotations
  4. import re
  5. import sys
  6. from pathlib import Path
  7. ROOT = Path(__file__).resolve().parent.parent
  8. REPOSITORY_RE = re.compile(
  9. r"^\| \[([^]]+)\]\(https://github\.com/full-stack-skills/([^)]+)\) \| ([0-9]+) \|",
  10. re.MULTILINE,
  11. )
  12. def fail(message: str) -> None:
  13. print(f"ERROR: {message}", file=sys.stderr)
  14. def read_inventory() -> list[str]:
  15. return [
  16. line.strip()
  17. for line in (ROOT / "scripts/repositories.txt").read_text(encoding="utf-8").splitlines()
  18. if line.strip() and not line.lstrip().startswith("#")
  19. ]
  20. def validate_readme(path: Path, expected: set[str]) -> int:
  21. text = path.read_text(encoding="utf-8")
  22. rows = REPOSITORY_RE.findall(text)
  23. repositories = [repository for label, repository, _ in rows if label == repository]
  24. counts = [int(count) for label, repository, count in rows if label == repository]
  25. errors = 0
  26. duplicates = sorted({name for name in repositories if repositories.count(name) > 1})
  27. missing = sorted(expected - set(repositories))
  28. unexpected = sorted(set(repositories) - expected)
  29. for label, values in (("duplicate", duplicates), ("missing", missing), ("unexpected", unexpected)):
  30. if values:
  31. fail(f"{path.name}: {label} repositories: {', '.join(values)}")
  32. errors += 1
  33. headline = re.search(
  34. r"\*\*([0-9]+)(?: 个)? Agent Skills[。.]\s*([0-9]+)(?: 个技能包| Skill Packages)",
  35. text,
  36. )
  37. if not headline:
  38. fail(f"{path.name}: headline counts not found")
  39. return errors + 1
  40. declared_skills, declared_packages = map(int, headline.groups())
  41. if declared_packages != len(expected):
  42. fail(f"{path.name}: declares {declared_packages} packages, expected {len(expected)}")
  43. errors += 1
  44. if declared_skills != sum(counts):
  45. fail(f"{path.name}: declares {declared_skills} skills, table sums to {sum(counts)}")
  46. errors += 1
  47. print(f"{path.name}: {len(repositories)} packages, {sum(counts)} skills")
  48. return errors
  49. def main() -> int:
  50. inventory = read_inventory()
  51. if inventory != sorted(set(inventory)):
  52. fail("scripts/repositories.txt must be sorted and unique")
  53. return 1
  54. expected = set(inventory)
  55. errors = sum(validate_readme(ROOT / name, expected) for name in ("README.md", "README.en.md"))
  56. return 1 if errors else 0
  57. if __name__ == "__main__":
  58. raise SystemExit(main())