towishy_Owen-Graphite/dev/scripts/sync_obsidian_theme.py
2026-05-17 09:31:23 +09:00

117 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""Bundle and sync Owen Graphite v3 into an Obsidian theme folder.
Re-bundles src/ via bundle_v3.py, promotes the bundle to theme.css, and
copies the release assets into <vault>/.obsidian/themes/Owen Graphite.
Reload Obsidian (Ctrl+R) after sync.
"""
from __future__ import annotations
import argparse
import importlib.util
import shutil
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
THEME_FOLDER_NAME = "Owen Graphite"
RELEASE_ASSETS = [
"theme.css",
"manifest.json",
"README.md",
"CHANGELOG.md",
"LICENSE",
"screenshots/light.png",
"screenshots/dark.png",
"screenshots/report.png",
]
DEFAULT_VAULTS = [
Path(r"H:\Obsidian"),
Path(r"D:\JAELE\Obsidian"),
Path.home() / "Work" / "Obsidian",
Path.home() / "work" / "Obsidian",
]
def load_module(name: str, path: Path):
spec = importlib.util.spec_from_file_location(name, path)
if spec is None or spec.loader is None:
raise RuntimeError(f"unable to load {path.relative_to(ROOT)}")
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
def candidate_targets() -> list[Path]:
return [vault / ".obsidian" / "themes" / THEME_FOLDER_NAME for vault in DEFAULT_VAULTS]
def find_target(explicit: Path | None) -> Path:
if explicit:
return explicit
existing = [path for path in candidate_targets() if path.exists()]
if existing:
return existing[0]
candidates = "\n".join(f"- {path}" for path in candidate_targets())
raise SystemExit(
"ERROR: no Obsidian target theme folder found. Pass --target explicitly.\n"
f"Checked:\n{candidates}"
)
def bundle_and_promote() -> None:
bundle = load_module("bundle_v3", ROOT / "dev" / "scripts" / "bundle_v3.py")
result = bundle.main()
if result:
raise RuntimeError("bundle_v3.py failed")
src = ROOT / "dist" / "theme-v3.css"
dst = ROOT / "theme.css"
shutil.copy2(src, dst)
print(f"OK: promoted {src.relative_to(ROOT)} -> theme.css")
def copy_assets(target: Path, dry_run: bool) -> None:
for rel in RELEASE_ASSETS:
source = ROOT / rel
if not source.is_file() or source.stat().st_size == 0:
raise FileNotFoundError(f"missing release asset: {rel}")
destination = target / rel
print(f"{'DRY-RUN' if dry_run else 'COPY'}: {rel} -> {destination}")
if dry_run:
continue
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, destination)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--target", type=Path, help="Obsidian theme folder to sync, e.g. H:\\Obsidian\\.obsidian\\themes\\Owen Graphite")
parser.add_argument("--dry-run", action="store_true", help="Show copy operations without writing files.")
parser.add_argument("--skip-bundle", action="store_true", help="Reuse existing theme.css; skip bundle + promote.")
parser.add_argument("--list-targets", action="store_true", help="Print known target candidates and exit.")
args = parser.parse_args()
if args.list_targets:
for path in candidate_targets():
marker = "exists" if path.exists() else "missing"
print(f"{marker}: {path}")
return 0
target = find_target(args.target)
print(f"Target: {target}")
if not args.skip_bundle:
bundle_and_promote()
copy_assets(target, args.dry_run)
if not args.dry_run:
print(f"OK: synced Owen Graphite to {target}")
return 0
if __name__ == "__main__":
raise SystemExit(main())