117 lines
3.6 KiB
Python
117 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Fail on new production YAMS_* surfaces or duplicate TOML reader definitions."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
SOURCE_ROOTS = ("src", "include", "tools")
|
|
SOURCE_SUFFIXES = {".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp"}
|
|
ENV_LITERAL_RE = re.compile(r"[\"'](YAMS_[A-Z0-9_]+)[\"']")
|
|
TOML_DEFINITION_RE = re.compile(
|
|
r"(?P<name>[A-Za-z_]\w*(?:[Tt][Oo][Mm][Ll])\w*)\s*"
|
|
r"\([^;{}]*\)\s*(?:const\s*)?\{",
|
|
re.MULTILINE,
|
|
)
|
|
|
|
|
|
def iter_sources(root: Path):
|
|
for relative_root in SOURCE_ROOTS:
|
|
source_root = root / relative_root
|
|
if not source_root.is_dir():
|
|
continue
|
|
for path in source_root.rglob("*"):
|
|
if path.is_file() and path.suffix.lower() in SOURCE_SUFFIXES:
|
|
yield path
|
|
|
|
|
|
def read_allowlist(path: Path) -> set[str]:
|
|
return {
|
|
line.strip()
|
|
for line in path.read_text(encoding="utf-8").splitlines()
|
|
if line.strip() and not line.lstrip().startswith("#")
|
|
}
|
|
|
|
|
|
def production_environment_keys(root: Path) -> set[str]:
|
|
keys: set[str] = set()
|
|
for path in iter_sources(root):
|
|
keys.update(
|
|
ENV_LITERAL_RE.findall(path.read_text(encoding="utf-8", errors="ignore"))
|
|
)
|
|
return keys
|
|
|
|
|
|
def toml_reader_definitions(root: Path) -> set[str]:
|
|
definitions: set[str] = set()
|
|
for path in iter_sources(root):
|
|
if path.suffix.lower() in {".h", ".hh", ".hpp"}:
|
|
continue
|
|
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
for match in TOML_DEFINITION_RE.finditer(text):
|
|
line = text.count("\n", 0, match.start()) + 1
|
|
relative = path.relative_to(root).as_posix()
|
|
definitions.add(f"{relative}:{match.group('name')}:{line}")
|
|
return definitions
|
|
|
|
|
|
def normalized_definition(entry: str) -> str:
|
|
path, name, _line = entry.rsplit(":", 2)
|
|
return f"{path}:{name}"
|
|
|
|
|
|
def run(root: Path, environment_allowlist: Path, reader_allowlist: Path) -> int:
|
|
allowed_keys = read_allowlist(environment_allowlist)
|
|
current_keys = production_environment_keys(root)
|
|
unknown_keys = sorted(current_keys - allowed_keys)
|
|
|
|
allowed_readers = read_allowlist(reader_allowlist)
|
|
definitions = sorted(toml_reader_definitions(root))
|
|
unknown_readers = [
|
|
definition
|
|
for definition in definitions
|
|
if normalized_definition(definition) not in allowed_readers
|
|
]
|
|
|
|
if unknown_keys:
|
|
print(
|
|
"new production YAMS_* literals require typed-config review:",
|
|
file=sys.stderr,
|
|
)
|
|
for key in unknown_keys:
|
|
print(f" {key}", file=sys.stderr)
|
|
if unknown_readers:
|
|
print(
|
|
"new TOML reader definitions must use parse_simple_toml:", file=sys.stderr
|
|
)
|
|
for definition in unknown_readers:
|
|
print(f" {definition}", file=sys.stderr)
|
|
if unknown_keys or unknown_readers:
|
|
return 1
|
|
|
|
print(
|
|
"configuration authority policy: "
|
|
f"{len(current_keys)} reviewed YAMS_* literal(s), "
|
|
f"{len(definitions)} reviewed TOML reader definition(s), 0 new"
|
|
)
|
|
return 0
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--root", type=Path, required=True)
|
|
parser.add_argument("--environment-allowlist", type=Path, required=True)
|
|
parser.add_argument("--reader-allowlist", type=Path, required=True)
|
|
args = parser.parse_args()
|
|
return run(
|
|
args.root.resolve(),
|
|
args.environment_allowlist.resolve(),
|
|
args.reader_allowlist.resolve(),
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|