107 lines
3.7 KiB
Python
Executable File
107 lines
3.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Generate a JSON manifest for addon plugins."""
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import inspect
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT_DIR = Path(__file__).resolve().parents[1]
|
|
PLUGIN_DIR = ROOT_DIR / "addon" / "plugins"
|
|
OUTPUT_PATH = ROOT_DIR / "docs" / "PLUGIN_MANIFEST.json"
|
|
|
|
sys.path.insert(0, str(ROOT_DIR / "addon"))
|
|
|
|
try:
|
|
from plugin_interface import BasisPlugin # type: ignore
|
|
except Exception as exc: # pragma: no cover
|
|
raise SystemExit(f"Failed to import BasisPlugin: {exc}")
|
|
|
|
|
|
def _import_module(path: Path):
|
|
spec = importlib.util.spec_from_file_location(path.stem, path)
|
|
if spec is None or spec.loader is None:
|
|
raise ImportError(f"Missing spec for {path}")
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[spec.name] = module
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def _collect_plugins():
|
|
plugins = []
|
|
for file_path in sorted(PLUGIN_DIR.glob("*.py")):
|
|
if file_path.name.startswith("_"):
|
|
continue
|
|
entry = {
|
|
"file": str(file_path.relative_to(ROOT_DIR)),
|
|
"module": file_path.stem,
|
|
"name": None,
|
|
"class": None,
|
|
"version": None,
|
|
"capabilities": [],
|
|
"prefer_source_metadata": False,
|
|
"base_url_setting": None,
|
|
"available": None,
|
|
"unavailable_reason": None,
|
|
"error": None,
|
|
}
|
|
try:
|
|
module = _import_module(file_path)
|
|
preferred = getattr(module, "Plugin", None)
|
|
if inspect.isclass(preferred) and issubclass(preferred, BasisPlugin) and preferred is not BasisPlugin:
|
|
classes = [preferred]
|
|
else:
|
|
classes = [
|
|
obj
|
|
for obj in module.__dict__.values()
|
|
if inspect.isclass(obj) and issubclass(obj, BasisPlugin) and obj is not BasisPlugin
|
|
]
|
|
classes.sort(key=lambda cls: cls.__name__.casefold())
|
|
|
|
if not classes:
|
|
entry["error"] = "No plugin classes found"
|
|
plugins.append(entry)
|
|
continue
|
|
|
|
cls = classes[0]
|
|
instance = cls()
|
|
entry["class"] = cls.__name__
|
|
entry["name"] = str(getattr(instance, "name", "") or "") or None
|
|
entry["version"] = str(getattr(instance, "version", "0.0.0") or "0.0.0")
|
|
entry["prefer_source_metadata"] = bool(getattr(instance, "prefer_source_metadata", False))
|
|
entry["available"] = bool(getattr(instance, "is_available", True))
|
|
entry["unavailable_reason"] = getattr(instance, "unavailable_reason", None)
|
|
try:
|
|
caps = instance.capabilities() # type: ignore[call-arg]
|
|
entry["capabilities"] = sorted([str(c) for c in caps]) if caps else []
|
|
except Exception:
|
|
entry["capabilities"] = []
|
|
|
|
entry["base_url_setting"] = getattr(module, "SETTING_BASE_URL", None)
|
|
except Exception as exc: # pragma: no cover
|
|
entry["error"] = str(exc)
|
|
plugins.append(entry)
|
|
|
|
plugins.sort(key=lambda item: (item.get("name") or item["module"]).casefold())
|
|
return plugins
|
|
|
|
|
|
def main() -> int:
|
|
if not PLUGIN_DIR.exists():
|
|
raise SystemExit("Plugin directory missing")
|
|
manifest = {
|
|
"schema_version": 1,
|
|
"plugins": _collect_plugins(),
|
|
}
|
|
OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
OUTPUT_PATH.write_text(json.dumps(manifest, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
print(str(OUTPUT_PATH))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|