forked from ChelseaKR/tods-validate
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgenerate_rules_doc.py
More file actions
99 lines (87 loc) · 3.34 KB
/
Copy pathgenerate_rules_doc.py
File metadata and controls
99 lines (87 loc) · 3.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#!/usr/bin/env python3
"""Generate docs/rules.md from the rule registry.
Run with --check (as CI does) to fail if the committed file has drifted from
the registry instead of rewriting it.
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
from tods_validate.findings import Severity
from tods_validate.rules import EXAMPLES, all_rules, render_example_markdown
from tods_validate.schema import SPEC_VERSION
DOC_PATH = Path(__file__).parent.parent / "docs" / "rules.md"
_BANDS = {
"1": "Package and file structure",
"2": "Field values",
"3": "References between files",
"4": "Semantic checks",
"5": "Coverage (opt-in, informational)",
"6": "Advisory (opt-in)",
}
def generate() -> str:
lines = [
"# Rule catalog",
"",
"<!-- Generated by scripts/generate_rules_doc.py; do not edit by hand. -->",
"",
f"All rules below validate against TODS v{SPEC_VERSION}. Severities:",
"",
"- **ERROR**: the feed violates the spec; consumers may misread or drop data.",
"- **WARNING**: probably a mistake, but the spec does not forbid it.",
"- **INFO**: worth knowing; no action required.",
"",
"Rules that resolve IDs into the companion GTFS feed run only when one is",
"available (via `--gtfs` or GTFS files alongside the TODS files).",
"",
]
rules = sorted(all_rules(), key=lambda r: r.id.split("-")[1][1:])
for band, heading in _BANDS.items():
lines.append(f"## {heading} (TODS-x{band}xx)")
lines.append("")
for r in rules:
if r.id.split("-")[1][1] != band:
continue
severity = Severity[r.severity.name].name
needs = " Needs a companion GTFS feed." if r.needs_gtfs else ""
optin = (
f" Opt-in: off by default, enable with `--enable {r.category}` or "
f"`--enable {r.id}`."
if not r.default_enabled
else ""
)
lines.append(f"### {r.id}: {r.title}")
lines.append("")
lines.append(f"Severity: {severity}.{needs}{optin}")
lines.append("")
lines.append(r.description)
lines.append("")
if r.interpretation:
lines.append(f"Interpretation: {r.interpretation}")
lines.append("")
example = EXAMPLES.get(r.id)
if example is not None:
lines.extend(render_example_markdown(example))
lines.append("")
lines.append(f"Spec reference: <{r.spec_section}>")
lines.append("")
return "\n".join(lines)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--check", action="store_true", help="fail if docs/rules.md is stale")
args = parser.parse_args()
content = generate()
if args.check:
if not DOC_PATH.exists() or DOC_PATH.read_text(encoding="utf-8") != content:
print(
"docs/rules.md is out of date; run scripts/generate_rules_doc.py",
file=sys.stderr,
)
return 1
print("docs/rules.md is up to date")
return 0
DOC_PATH.write_text(content, encoding="utf-8")
print(f"wrote {DOC_PATH}")
return 0
if __name__ == "__main__":
raise SystemExit(main())