PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
audit_page_types.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Enforce that every published documentation page has a declared page type."""
3
4from __future__ import annotations
5
6import json
7import re
8import sys
9from pathlib import Path
10
11
12REPO_ROOT = Path(__file__).resolve().parents[2]
13REGISTRY = REPO_ROOT / "tests" / "tooling" / "page_types.json"
14HTML_DIR = REPO_ROOT / "docs_build" / "html"
15PAGE_DIRS = ("docs/pages", "docs")
16# Authoring templates use a literal placeholder rather than a real page id.
17PLACEHOLDER_IDS = {"<ID>", "<id>"}
18
19
20def declared_pages() -> dict:
21 """!
22 @brief Every `@page` id in the documentation sources, with its file.
23 @return Mapping of page id to source path.
24 """
25 pages: dict = {}
26 for directory in PAGE_DIRS:
27 for markdown in sorted((REPO_ROOT / directory).glob("*.md")):
28 match = re.search(r"^@page\s+(\S+)", markdown.read_text(encoding="utf-8"), re.M)
29 if match and match.group(1) not in PLACEHOLDER_IDS:
30 pages.setdefault(match.group(1), markdown)
31 return pages
32
33
34def inline_type(path: Path) -> str:
35 """!
36 @brief The page type a page declares inline, if any.
37 @param[in] path Page source.
38 @return Declared type, or an empty string.
39 """
40 match = re.search(r"@pagemeta\{([^,}]+)", path.read_text(encoding="utf-8"))
41 return match.group(1).strip() if match else ""
42
43
44def main() -> int:
45 """!
46 @brief Fail when a published page is untyped or its declarations disagree.
47
48 @details Coverage is enforced against the pages the build actually publishes. A
49 central registry types all of them without adding repetitive chrome to each
50 page; where a page also declares its type inline, the two must agree.
51 @return Process status code.
52 """
53 registry = json.loads(REGISTRY.read_text(encoding="utf-8"))
54 valid = set(registry["valid_types"])
55 assignments = registry["assignments"]
56 pages = declared_pages()
57
58 published = {
59 page_id for page_id in pages if (HTML_DIR / f"{page_id}.html").is_file()
60 } or set(pages)
61
62 problems: list = []
63 for page_id in sorted(published):
64 assigned = assignments.get(page_id)
65 if not assigned:
66 problems.append(f"{pages[page_id].name}: page '{page_id}' has no type assignment")
67 continue
68 if assigned not in valid:
69 problems.append(f"{page_id}: type '{assigned}' is not one of {sorted(valid)}")
70 continue
71 declared = inline_type(pages[page_id])
72 if declared and declared != assigned:
73 problems.append(
74 f"{pages[page_id].name}: inline @pagemeta says '{declared}' but the registry "
75 f"says '{assigned}'"
76 )
77 for stale in sorted(set(assignments) - set(pages)):
78 problems.append(f"registry assigns a type to '{stale}', which is not a declared page")
79
80 if problems:
81 print("Page-type coverage violations:", file=sys.stderr)
82 for problem in problems:
83 print(f" {problem}", file=sys.stderr)
84 print(
85 "\nAssign a type in tests/tooling/page_types.json. See 63_Page_Type_Contract for\n"
86 "what each type owes the reader.",
87 file=sys.stderr,
88 )
89 return 1
90
91 from collections import Counter
92 spread = Counter(assignments[p] for p in published)
93 summary = ", ".join(f"{count} {kind}" for kind, count in sorted(spread.items()))
94 print(f"Page-type audit passed: {len(published)} published pages typed ({summary}).")
95 return 0
96
97
98if __name__ == "__main__":
99 raise SystemExit(main())
dict declared_pages()
Every @page id in the documentation sources, with its file.
int main()
Fail when a published page is untyped or its declarations disagree.
str inline_type(Path path)
The page type a page declares inline, if any.