Niveus/tools/check.py
Nexoniarz 23b8dd376d Niveus: a tactile, adaptive interface language
Bevelled controls with light falling across them, fields as wells, solid
windows, and glass kept for the chrome that floats over everything else.
Warm graphite in dark, soft paper in light, one brand color driving both.
Ships its own fonts (Instrument Sans, JetBrains Mono; OFL).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 17:47:03 +02:00

255 lines
9 KiB
Python
Executable file

#!/usr/bin/env python3
"""Check that the Niveus sources hold to their own rules.
Four things go wrong in a design system long before anything looks broken, and
all four are mechanical:
1. A semantic token is defined for one appearance but not the other, so a
role silently resolves to nothing in light or in dark.
2. A component references a token that does not exist — a typo that degrades
to "no style" instead of to an error.
3. A visual value is hard-coded in a component — or in an example, which
claims to be built from the system and nothing else — instead of coming
from a token, which is how a system stops being a single source of truth.
4. dist/ drifts from the sources.
This script catches the first three; `build.py --check` covers the fourth.
python3 tools/check.py
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
SRC = ROOT / "niveus"
DEFINITION = re.compile(r"(--[a-zA-Z0-9_-]+)\s*:")
REFERENCE = re.compile(r"var\(\s*(--[a-zA-Z0-9_-]+)")
COMMENT = re.compile(r"/\*.*?\*/", re.DOTALL)
# A raw color or a raw shadow geometry in a component means the design language
# was restated locally instead of referenced.
HARD_CODED = [
(re.compile(r"#[0-9a-fA-F]{3,8}\b"), "hard-coded color"),
(re.compile(r"\b(?:rgb|rgba|hsl|hsla|oklch|lab)\(", re.IGNORECASE), "hard-coded color"),
]
# Properties the *application* supplies per instance, not the system. A menu
# and its trigger have to name the same anchor, and only the markup knows which
# pair belongs together.
CONSUMER_DEFINED = {"--nv-anchor"}
TOKEN_FILES = sorted((SRC / "tokens").glob("*.css")) + sorted((SRC / "themes").glob("*.css"))
CONSUMER_FILES = (
sorted((SRC / "components").glob("*.css"))
+ [SRC / "base.css", SRC / "utilities.css"]
)
def strip_comments(text: str) -> str:
return COMMENT.sub("", text)
def collect(paths: list[Path]) -> tuple[set[str], dict[str, list[tuple[Path, int, str]]]]:
"""Return every custom property defined across `paths`, and every reference."""
defined: set[str] = set()
references: dict[str, list[tuple[Path, int, str]]] = {}
for path in paths:
source = strip_comments(path.read_text(encoding="utf-8"))
for line_no, line in enumerate(source.split("\n"), start=1):
for name in DEFINITION.findall(line):
defined.add(name)
for name in REFERENCE.findall(line):
references.setdefault(name, []).append((path, line_no, line.strip()))
return defined, references
def rel(path: Path) -> str:
return str(path.relative_to(ROOT))
def check_theme_parity(failures: list[str]) -> None:
light = SRC / "themes" / "light.css"
dark = SRC / "themes" / "dark.css"
light_roles = {
name[len("--nv-light-") :]
for name in DEFINITION.findall(strip_comments(light.read_text(encoding="utf-8")))
if name.startswith("--nv-light-")
}
dark_roles = {
name[len("--nv-dark-") :]
for name in DEFINITION.findall(strip_comments(dark.read_text(encoding="utf-8")))
if name.startswith("--nv-dark-")
}
for role in sorted(light_roles - dark_roles):
failures.append(f"themes/dark.css: missing --nv-dark-{role} (light defines it)")
for role in sorted(dark_roles - light_roles):
failures.append(f"themes/light.css: missing --nv-light-{role} (dark defines it)")
if not failures:
print(f" theme parity: {len(light_roles)} roles defined in both appearances")
def check_references(failures: list[str]) -> None:
all_files = TOKEN_FILES + CONSUMER_FILES
defined, references = collect(all_files)
unresolved = []
for name, sites in sorted(references.items()):
if name.startswith("--_"):
continue # component-private, defined and used in the same rule
if name in CONSUMER_DEFINED:
continue
if name in defined:
continue
# var() with a fallback is an intentional escape hatch.
if all("," in line.split(f"var({name}")[1][:40] for _, _, line in sites):
continue
unresolved.append((name, sites[0]))
for name, (path, line_no, _) in unresolved:
failures.append(f"{rel(path)}:{line_no}: {name} is referenced but never defined")
if not unresolved:
print(f" references: {len(references)} tokens referenced, all resolve")
def check_hard_coded(failures: list[str]) -> None:
found = 0
for path in CONSUMER_FILES:
source = strip_comments(path.read_text(encoding="utf-8"))
for line_no, line in enumerate(source.split("\n"), start=1):
for pattern, label in HARD_CODED:
if pattern.search(line):
failures.append(f"{rel(path)}:{line_no}: {label} — {line.strip()}")
found += 1
if not found:
print(f" no hard-coded colors in {len(CONSUMER_FILES)} component and base files")
REFERENCE_DOC = ROOT / "REFERENCE.md"
EXAMPLES = ROOT / "examples"
# The one literal color an example is allowed: the input to the system.
BRAND_INPUT = re.compile(r"--nv-brand\s*:")
STYLE_BLOCK = re.compile(r"<style[^>]*>(.*?)</style>", re.DOTALL | re.IGNORECASE)
INLINE_STYLE = re.compile(r"""\sstyle=["']([^"']*)["']""")
def check_examples(failures: list[str]) -> None:
"""The examples claim to be built from the system and nothing else.
That claim is only worth making if it is enforced: an example that quietly
reaches for a hex value is no longer demonstrating the system, it is
demonstrating how to work around one. The single exception is --nv-brand,
which is the input the whole color system derives from — a literal there is
the point.
"""
if not EXAMPLES.is_dir():
return
scanned = 0
for path in sorted(EXAMPLES.rglob("*")):
if path.suffix not in {".css", ".html"}:
continue
scanned += 1
raw = path.read_text(encoding="utf-8")
if path.suffix == ".css":
regions = [(1, strip_comments(raw))]
else:
# Only the parts of an HTML file that can carry style.
regions = []
for match in STYLE_BLOCK.finditer(raw):
line = raw[: match.start()].count("\n") + 1
regions.append((line, strip_comments(match.group(1))))
for match in INLINE_STYLE.finditer(raw):
line = raw[: match.start()].count("\n") + 1
regions.append((line, match.group(1)))
for base_line, region in regions:
for offset, line in enumerate(region.split("\n")):
if BRAND_INPUT.search(line):
continue
for pattern, label in HARD_CODED:
if pattern.search(line):
failures.append(
f"{rel(path)}:{base_line + offset}: {label} in an example "
f"— {line.strip()[:70]}"
)
if path.name == "index.html" and "dist/niveus.css" not in raw:
failures.append(f"{rel(path)}: does not link the system stylesheet")
if not failures:
print(f" examples: {scanned} files, no color stated outside --nv-brand")
def check_reference(failures: list[str]) -> None:
"""Every class the CSS defines must appear in REFERENCE.md, spelled in full.
The reference claims to be the entire API — which is the only reason it is
safe to hand to someone, or to a model, instead of the source. A class that
exists but is undocumented breaks that claim silently, and a documented
class that no longer exists is worse: it reads as real.
"""
if not REFERENCE_DOC.is_file():
return
documented = REFERENCE_DOC.read_text(encoding="utf-8")
defined: set[str] = set()
sources = (
sorted((SRC / "components").glob("*.css"))
+ [SRC / "utilities.css", SRC / "base.css", SRC / "continuity.css"]
)
for path in sources:
source = strip_comments(path.read_text(encoding="utf-8"))
defined.update(re.findall(r"\.(nv-[a-z0-9_-]+)", source))
missing = sorted(name for name in defined if f".{name}" not in documented)
for name in missing:
failures.append(f"REFERENCE.md: .{name} is defined in the CSS but not documented")
# And the reverse: nothing documented that no longer exists.
mentioned = set(re.findall(r"`\.(nv-[a-z0-9_-]+)`", documented))
for name in sorted(mentioned - defined):
failures.append(f"REFERENCE.md: .{name} is documented but not defined anywhere")
if not missing:
print(f" reference: {len(defined)} classes, all documented")
def main() -> int:
failures: list[str] = []
print("Niveus checks")
check_theme_parity(failures)
check_references(failures)
check_hard_coded(failures)
check_examples(failures)
check_reference(failures)
if failures:
print(f"\n{len(failures)} problem(s):")
for failure in failures:
print(f" {failure}")
return 1
print("\nAll checks passed.")
return 0
if __name__ == "__main__":
sys.exit(main())