96 lines
3.4 KiB
Python
96 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Icon validation + project sync (strategist GATE tool).
|
|
|
|
Strategist runs this while locking the icon inventory (design_spec.md §VI /
|
|
spec_lock.md `icons`). It does two things, in order of importance:
|
|
|
|
1. **Validate** each ``lib/name`` against the global icon library
|
|
(``templates/icons/``). A name that does not resolve to a real file is a
|
|
hard error — the script exits non-zero listing every miss, so the
|
|
strategist's "missing icon = re-pick now" GATE actually fires instead of
|
|
carrying a phantom icon into generation.
|
|
2. **Copy** each resolved icon into ``<project>/icons/<lib>/`` so the project
|
|
carries its own icon set. This is belt-and-suspenders: ``finalize_svg.py``
|
|
and the native ``svg_to_pptx`` converter both resolve project-first with a
|
|
fallback to the global library (see ``svg_finalize.embed_icons``), so the
|
|
copy is not strictly required for embedding — but it keeps the project
|
|
self-contained and validates existence on the spot.
|
|
|
|
Usage:
|
|
python3 scripts/icon_sync.py <project_path> <lib/name> [<lib/name> ...]
|
|
|
|
Example:
|
|
python3 scripts/icon_sync.py /workspace/ppt生成/deck tabler-outline/brain tabler-outline/cpu
|
|
|
|
Exit code:
|
|
0 every icon resolved (and copied)
|
|
1 one or more icons missing / unresolved
|
|
2 bad arguments
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import shutil
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Windows GBK console safety (mirrors other ppt scripts).
|
|
try: # pragma: no cover - platform dependent
|
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
|
except Exception: # pragma: no cover
|
|
pass
|
|
|
|
# Reuse the single source of truth for name resolution (handles the chunk/
|
|
# alias and the un-prefixed legacy layout) so validation matches what the
|
|
# embedder will actually load at finalize / export time.
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
from svg_finalize.embed_icons import resolve_icon_path # noqa: E402
|
|
|
|
GLOBAL_ICONS_DIR = Path(__file__).resolve().parent.parent / "templates" / "icons"
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
args = list(sys.argv[1:] if argv is None else argv)
|
|
if len(args) < 2:
|
|
print("usage: icon_sync.py <project_path> <lib/name> [<lib/name> ...]")
|
|
return 2
|
|
|
|
project_path = Path(args[0])
|
|
names = args[1:]
|
|
if not project_path.exists():
|
|
print(f"[ERROR] project path not found: {project_path}")
|
|
return 2
|
|
|
|
project_icons = project_path / "icons"
|
|
missing: list[str] = []
|
|
copied = 0
|
|
|
|
for name in names:
|
|
src, _base = resolve_icon_path(name, GLOBAL_ICONS_DIR)
|
|
if not src.exists():
|
|
missing.append(name)
|
|
print(f"[MISSING] {name} (no file under {GLOBAL_ICONS_DIR})")
|
|
continue
|
|
# Mirror into <project>/icons/<lib>/<name>.svg, preserving the lib dir.
|
|
rel = src.relative_to(GLOBAL_ICONS_DIR)
|
|
dst = project_icons / rel
|
|
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copyfile(src, dst)
|
|
copied += 1
|
|
print(f"[OK] {name} -> {dst}")
|
|
|
|
print()
|
|
print(f"[Summary] {copied} copied, {len(missing)} missing")
|
|
if missing:
|
|
print("[GATE] missing icons — re-pick real filenames via "
|
|
"`ls templates/icons/<lib>/ | grep <keyword>`, fix spec_lock.md, re-run:")
|
|
for m in missing:
|
|
print(f" - {m}")
|
|
return 1
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|