Release standalone PPT template workflow 0.5.0
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
PPT Master - Icon Sync
|
||||
|
||||
Copy chosen library icons into `<project>/icons/<lib>/` when selected. Missing
|
||||
names exit non-zero before export. Known basenames need no separate existence
|
||||
check; search the chosen library only for unresolved concepts.
|
||||
|
||||
Project-local custom icons count as satisfied. In one resource-selection batch,
|
||||
`simple-icons` may accompany one of the four stylistic libraries for
|
||||
real brand marks.
|
||||
|
||||
Usage:
|
||||
python3 scripts/icon_sync.py <project_path> <lib/name> [<lib/name> ...]
|
||||
|
||||
Examples:
|
||||
python3 scripts/icon_sync.py projects/deck tabler-outline/home tabler-outline/chart
|
||||
python3 scripts/icon_sync.py projects/deck tabler-outline/home 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"}
|
||||
_STYLISTIC_LIBRARIES = {
|
||||
"chunk-filled",
|
||||
"phosphor-duotone",
|
||||
"tabler-filled",
|
||||
"tabler-outline",
|
||||
}
|
||||
_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
|
||||
|
||||
requested_libraries = {_split_name(raw)[0] for raw in args.icons}
|
||||
stylistic_libraries = sorted(requested_libraries & _STYLISTIC_LIBRARIES)
|
||||
if len(stylistic_libraries) > 1:
|
||||
print(
|
||||
f"[ERROR] mixed stylistic icon libraries: {', '.join(stylistic_libraries)}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
print(
|
||||
"Choose one of the four stylistic libraries per selection batch; "
|
||||
"simple-icons may coexist for real brand marks.",
|
||||
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: rg --files "{_GLOBAL_ICONS_DIR / lib}" -g \'*<keyword>*.svg\')',
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user