mirror of
https://github.com/bitsycore/compose-desktop-native.git
synced 2026-10-05 10:47:26 +00:00
128 lines
4.6 KiB
Python
128 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Scan a Kotlin project for `MaterialSymbols.<Name>` references and emit a
|
|
usage-codepoint.txt that lists every name + codepoint actually used.
|
|
|
|
The file is what the Gradle `subsetMaterialSymbols<Style>` task reads to
|
|
decide which glyphs to keep in the bundled font. Splitting discovery
|
|
(this script, runs on the build machine's Python) from subsetting (the
|
|
Gradle task, runs hb-subset) lets the same usage file feed all three
|
|
style modules and lets you eyeball / diff it in CI without touching
|
|
fonts.
|
|
|
|
Output format - UTF-8 text, one entry per line:
|
|
|
|
# Generated by scripts/subset-material-symbols.py
|
|
# 142 icons referenced, 140 resolved, 2 unknown
|
|
Add=0xe145
|
|
ArrowBack=0xe5c4
|
|
...
|
|
# Unknown (not in MaterialSymbols.kt):
|
|
# SomeMissingName
|
|
|
|
Usage:
|
|
python3 scripts/subset-material-symbols.py \\
|
|
--src apidemo/src demo/src \\
|
|
--out build/icons/usage-codepoint.txt
|
|
|
|
Defaults to scanning the whole repo and writing to
|
|
build/icons/usage-codepoint.txt at the repo root.
|
|
"""
|
|
|
|
import argparse
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
|
|
# Pulls "Lock", "0xe897" out of `const val Lock = 0xe897`.
|
|
CONST_RE = re.compile(r"const\s+val\s+(\w+)\s*=\s*0x([0-9a-fA-F]+)")
|
|
# Matches MaterialSymbols.Name in any Kotlin source.
|
|
USAGE_RE = re.compile(r"\bMaterialSymbols\.(\w+)\b")
|
|
|
|
DEFAULT_CONSTANTS = REPO_ROOT / "utils/material-symbols/src/commonMain/kotlin/com/compose/sdl/icons/MaterialSymbols.kt"
|
|
DEFAULT_OUTPUT = REPO_ROOT / "build/icons/usage-codepoint.txt"
|
|
|
|
|
|
def parse_constants(path: Path) -> dict[str, int]:
|
|
"""Read MaterialSymbols.kt → {Name: codepoint}."""
|
|
icons: dict[str, int] = {}
|
|
with path.open(encoding="utf-8") as f:
|
|
for line in f:
|
|
m = CONST_RE.search(line)
|
|
if m:
|
|
icons[m.group(1)] = int(m.group(2), 16)
|
|
return icons
|
|
|
|
|
|
def scan_usages(src_roots: list[Path]) -> set[str]:
|
|
"""Walk each src root, grep every .kt file for MaterialSymbols.<Name>."""
|
|
used: set[str] = set()
|
|
for root in src_roots:
|
|
if not root.exists():
|
|
print(f"warn: source root {root} does not exist", file=sys.stderr)
|
|
continue
|
|
for path in root.rglob("*.kt"):
|
|
try:
|
|
text = path.read_text(encoding="utf-8")
|
|
except UnicodeDecodeError:
|
|
continue
|
|
for m in USAGE_RE.finditer(text):
|
|
used.add(m.group(1))
|
|
return used
|
|
|
|
|
|
def main() -> int:
|
|
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
ap.add_argument("--src", nargs="+", default=[],
|
|
help="Source root(s) to scan. Defaults to the entire repo.")
|
|
ap.add_argument("--constants", type=Path, default=DEFAULT_CONSTANTS,
|
|
help="Path to MaterialSymbols.kt (icon → codepoint map).")
|
|
ap.add_argument("--extra", default="",
|
|
help="Comma-separated extra icon NAMES to keep even if not detected in sources.")
|
|
ap.add_argument("--out", type=Path, default=DEFAULT_OUTPUT,
|
|
help="Output file (default: build/icons/usage-codepoint.txt).")
|
|
args = ap.parse_args()
|
|
|
|
src_roots = [Path(s).resolve() for s in args.src] or [REPO_ROOT]
|
|
|
|
constants = parse_constants(args.constants)
|
|
if not constants:
|
|
print(f"error: no codepoints parsed from {args.constants}", file=sys.stderr)
|
|
return 2
|
|
|
|
used_names = scan_usages(src_roots)
|
|
used_names.update(n.strip() for n in args.extra.split(",") if n.strip())
|
|
|
|
resolved: list[tuple[str, int]] = []
|
|
missing: list[str] = []
|
|
for name in sorted(used_names):
|
|
cp = constants.get(name)
|
|
if cp is None:
|
|
missing.append(name)
|
|
else:
|
|
resolved.append((name, cp))
|
|
|
|
args.out.parent.mkdir(parents=True, exist_ok=True)
|
|
with args.out.open("w", encoding="utf-8") as f:
|
|
f.write("# Generated by scripts/subset-material-symbols.py - do not edit.\n")
|
|
f.write(f"# {len(used_names)} icons referenced, {len(resolved)} resolved, {len(missing)} unknown\n")
|
|
f.write(f"# Scanned: {', '.join(str(r) for r in src_roots)}\n")
|
|
for name, cp in resolved:
|
|
f.write(f"{name}=0x{cp:04x}\n")
|
|
if missing:
|
|
f.write("# Unknown (not in MaterialSymbols.kt):\n")
|
|
for name in missing:
|
|
f.write(f"# {name}\n")
|
|
|
|
print(f"Wrote {args.out}")
|
|
print(f" {len(used_names)} referenced · {len(resolved)} resolved · {len(missing)} unknown")
|
|
if missing:
|
|
print(f" Unknown names: {', '.join(missing)}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|