Files
wehub-resource-sync 8ef9db2513
Deploy to GitHub Pages / deploy (push) Has been cancelled
Deploy to GitHub Pages / build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:56:49 +08:00

115 lines
3.9 KiB
Python

#!/usr/bin/env python3
"""
PPT Master - Icon Sync
Copy chosen library icons into a project's own `icons/` folder at the moment they
are selected. Run it with the icon names you are picking; each is copied from the
global library into `<project>/icons/<lib>/`. Any name the library does not have
is reported on the spot and the command exits non-zero — so you re-pick a valid
icon then, not at export time. Over-copying candidates is fine: finalize only
embeds the icons actually referenced by `<use data-icon>`, the rest sit unused.
Custom icons you place in `<project>/icons/<lib>/` yourself are honored too — a
name already present in the project is treated as satisfied, not missing.
Usage:
python3 scripts/icon_sync.py <project_path> <lib/name> [<lib/name> ...]
Examples:
python3 scripts/icon_sync.py projects/deck chunk-filled/home tabler-outline/chart
python3 scripts/icon_sync.py projects/deck simple-icons/github
Dependencies:
None (standard library only).
See references/executor-base.md §4 and templates/icons/README.md.
"""
from __future__ import annotations
import argparse
import shutil
import sys
from pathlib import Path
from typing import Optional
from console_encoding import configure_utf8_stdio
configure_utf8_stdio()
_LIB_ALIASES = {"chunk": "chunk-filled"}
_GLOBAL_ICONS_DIR = Path(__file__).resolve().parent.parent / "templates" / "icons"
def _split_name(icon_name: str) -> tuple[str, str]:
"""`lib/name` -> (lib, name), applying the chunk→chunk-filled alias."""
if "/" not in icon_name:
# legacy un-prefixed names live in chunk-filled/
return "chunk-filled", icon_name
lib, name = icon_name.split("/", 1)
return _LIB_ALIASES.get(lib, lib), name
def sync_icons(project_path: Path, icon_names: list[str], global_dir: Path = _GLOBAL_ICONS_DIR) -> tuple[list[str], list[str]]:
"""Copy each `lib/name` from the global library into `<project>/icons/`.
Returns (copied, missing). A name already present in the project (e.g. a
custom icon) counts as satisfied, not missing.
"""
project_icons = project_path / "icons"
copied: list[str] = []
missing: list[str] = []
for raw in icon_names:
lib, name = _split_name(raw)
src = global_dir / lib / f"{name}.svg"
dst = project_icons / lib / f"{name}.svg"
if src.is_file():
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src, dst)
copied.append(f"{lib}/{name}")
elif dst.is_file():
copied.append(f"{lib}/{name} (already in project)")
else:
missing.append(f"{lib}/{name}")
return copied, missing
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Copy chosen library icons into a project's icons/ folder; report missing ones.",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument("project_path", help="Project directory")
parser.add_argument("icons", nargs="+", help="Icon names to copy, e.g. chunk-filled/home")
return parser
def main(argv: Optional[list[str]] = None) -> int:
args = build_parser().parse_args(argv)
project = Path(args.project_path)
if not project.is_dir():
print(f"[ERROR] project not found: {project}", file=sys.stderr)
return 1
copied, missing = sync_icons(project, args.icons)
if copied:
print(f"[OK] {len(copied)} icon(s) in {project / 'icons'}:", file=sys.stderr)
for c in copied:
print(f" + {c}", file=sys.stderr)
if missing:
print(f"\n[MISSING] {len(missing)} icon(s) not in the library — re-pick before continuing:", file=sys.stderr)
for m in missing:
lib = m.split("/", 1)[0]
print(f" ✗ {m} (search: ls {_GLOBAL_ICONS_DIR}/{lib}/ | grep <keyword>)", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())