feat: unify card runtime and event-driven web ui
This commit is contained in:
parent
0edf8c3fef
commit
4dfb7ca3cc
105 changed files with 17382 additions and 8505 deletions
139
scripts/sync_card_templates.py
Normal file
139
scripts/sync_card_templates.py
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import filecmp
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
LIVE_TEMPLATES_DIR = Path.home() / ".nanobot" / "cards" / "templates"
|
||||
EXAMPLE_TEMPLATES_DIR = Path(__file__).resolve().parent.parent / "examples" / "cards" / "templates"
|
||||
|
||||
|
||||
def iter_template_dirs(root: Path) -> dict[str, Path]:
|
||||
templates: dict[str, Path] = {}
|
||||
if not root.exists():
|
||||
return templates
|
||||
for child in sorted(root.iterdir()):
|
||||
if not child.is_dir():
|
||||
continue
|
||||
if not (child / "manifest.json").exists():
|
||||
continue
|
||||
templates[child.name] = child
|
||||
return templates
|
||||
|
||||
|
||||
def sync_directory(source: Path, dest: Path) -> None:
|
||||
dest.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
source_entries = {path.name: path for path in source.iterdir()}
|
||||
dest_entries = {path.name: path for path in dest.iterdir()} if dest.exists() else {}
|
||||
|
||||
for name, source_path in source_entries.items():
|
||||
dest_path = dest / name
|
||||
if source_path.is_dir():
|
||||
sync_directory(source_path, dest_path)
|
||||
continue
|
||||
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
if not dest_path.exists() or not filecmp.cmp(source_path, dest_path, shallow=False):
|
||||
shutil.copy2(source_path, dest_path)
|
||||
|
||||
for name, dest_path in dest_entries.items():
|
||||
if name in source_entries:
|
||||
continue
|
||||
if dest_path.is_dir():
|
||||
shutil.rmtree(dest_path)
|
||||
else:
|
||||
dest_path.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def compare_directory(source: Path, dest: Path, drifts: list[str], *, prefix: str) -> None:
|
||||
source_entries = {path.name: path for path in source.iterdir()} if source.exists() else {}
|
||||
dest_entries = {path.name: path for path in dest.iterdir()} if dest.exists() else {}
|
||||
|
||||
for name, source_path in source_entries.items():
|
||||
rel = f"{prefix}/{name}" if prefix else name
|
||||
dest_path = dest / name
|
||||
if name not in dest_entries:
|
||||
drifts.append(f"missing mirror: {rel}")
|
||||
continue
|
||||
if source_path.is_dir():
|
||||
if not dest_path.is_dir():
|
||||
drifts.append(f"type mismatch: {rel}")
|
||||
continue
|
||||
compare_directory(source_path, dest_path, drifts, prefix=rel)
|
||||
continue
|
||||
if dest_path.is_dir():
|
||||
drifts.append(f"type mismatch: {rel}")
|
||||
continue
|
||||
if not filecmp.cmp(source_path, dest_path, shallow=False):
|
||||
drifts.append(f"content drift: {rel}")
|
||||
|
||||
for name in dest_entries:
|
||||
if name not in source_entries:
|
||||
rel = f"{prefix}/{name}" if prefix else name
|
||||
drifts.append(f"stale mirror: {rel}")
|
||||
|
||||
|
||||
def run_check(source_root: Path, dest_root: Path) -> int:
|
||||
drifts: list[str] = []
|
||||
source_templates = iter_template_dirs(source_root)
|
||||
dest_templates = iter_template_dirs(dest_root)
|
||||
|
||||
for name, source_dir in source_templates.items():
|
||||
dest_dir = dest_root / name
|
||||
if name not in dest_templates:
|
||||
drifts.append(f"missing mirror: {name}")
|
||||
continue
|
||||
compare_directory(source_dir, dest_dir, drifts, prefix=name)
|
||||
|
||||
for name in dest_templates:
|
||||
if name not in source_templates:
|
||||
drifts.append(f"stale mirror: {name}")
|
||||
|
||||
if drifts:
|
||||
for drift in drifts:
|
||||
print(drift)
|
||||
return 1
|
||||
print("template sync ok")
|
||||
return 0
|
||||
|
||||
|
||||
def run_sync(source_root: Path, dest_root: Path) -> int:
|
||||
source_templates = iter_template_dirs(source_root)
|
||||
dest_root.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for name, source_dir in source_templates.items():
|
||||
sync_directory(source_dir, dest_root / name)
|
||||
|
||||
for child in list(dest_root.iterdir()):
|
||||
if child.name in source_templates:
|
||||
continue
|
||||
if child.is_dir():
|
||||
shutil.rmtree(child)
|
||||
else:
|
||||
child.unlink(missing_ok=True)
|
||||
|
||||
print(f"synced {len(source_templates)} template directories")
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--check", action="store_true")
|
||||
parser.add_argument("--source", type=Path, default=LIVE_TEMPLATES_DIR)
|
||||
parser.add_argument("--dest", type=Path, default=EXAMPLE_TEMPLATES_DIR)
|
||||
args = parser.parse_args()
|
||||
|
||||
source_root = args.source.expanduser()
|
||||
dest_root = args.dest.expanduser()
|
||||
|
||||
if args.check:
|
||||
return run_check(source_root, dest_root)
|
||||
return run_sync(source_root, dest_root)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue