2025-11-25 20:09:33 -08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2026-01-01 20:37:27 -08:00
|
|
|
import os
|
|
|
|
|
import sys
|
|
|
|
|
from typing import Any, Callable, Dict, Iterable, Iterator, Sequence
|
2025-11-25 20:09:33 -08:00
|
|
|
from importlib import import_module as _import_module
|
|
|
|
|
|
|
|
|
|
# A cmdlet is a callable taking (result, args, config) -> int
|
|
|
|
|
Cmdlet = Callable[[Any, Sequence[str], Dict[str, Any]], int]
|
|
|
|
|
|
|
|
|
|
# Registry of command-name -> cmdlet function
|
2025-12-29 18:42:02 -08:00
|
|
|
REGISTRY: Dict[str,
|
|
|
|
|
Cmdlet] = {}
|
2025-11-25 20:09:33 -08:00
|
|
|
|
|
|
|
|
|
2025-12-12 21:55:38 -08:00
|
|
|
def _normalize_cmd_name(name: str) -> str:
|
2025-12-29 17:05:03 -08:00
|
|
|
return str(name or "").replace("_", "-").lower().strip()
|
2025-12-12 21:55:38 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def register_callable(names: Iterable[str], fn: Cmdlet) -> Cmdlet:
|
|
|
|
|
"""Register a callable under one or more command names.
|
|
|
|
|
|
|
|
|
|
This is the single registration mechanism used by both:
|
|
|
|
|
- legacy function cmdlet (decorator form)
|
|
|
|
|
- class-based cmdlet (Cmdlet.register())
|
|
|
|
|
"""
|
|
|
|
|
for name in names:
|
|
|
|
|
key = _normalize_cmd_name(name)
|
|
|
|
|
if key:
|
|
|
|
|
REGISTRY[key] = fn
|
|
|
|
|
return fn
|
|
|
|
|
|
|
|
|
|
|
2025-11-25 20:09:33 -08:00
|
|
|
def register(names: Iterable[str]):
|
|
|
|
|
"""Decorator to register a function under one or more command names.
|
|
|
|
|
|
|
|
|
|
Usage:
|
2025-12-11 23:21:45 -08:00
|
|
|
@register(["add-tags"])
|
2025-11-25 20:09:33 -08:00
|
|
|
def _run(result, args, config) -> int: ...
|
|
|
|
|
"""
|
2025-12-29 17:05:03 -08:00
|
|
|
|
2025-11-25 20:09:33 -08:00
|
|
|
def _wrap(fn: Cmdlet) -> Cmdlet:
|
2025-12-12 21:55:38 -08:00
|
|
|
return register_callable(names, fn)
|
2025-12-29 17:05:03 -08:00
|
|
|
|
2025-11-25 20:09:33 -08:00
|
|
|
return _wrap
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get(cmd_name: str) -> Cmdlet | None:
|
2025-12-12 21:55:38 -08:00
|
|
|
return REGISTRY.get(_normalize_cmd_name(cmd_name))
|
2025-11-25 20:09:33 -08:00
|
|
|
|
|
|
|
|
|
2026-01-01 20:37:27 -08:00
|
|
|
_MODULES_LOADED = False
|
|
|
|
|
|
|
|
|
|
def _iter_cmdlet_module_names() -> Iterator[str]:
|
|
|
|
|
cmdlet_dir = os.path.dirname(__file__)
|
|
|
|
|
try:
|
|
|
|
|
entries = os.listdir(cmdlet_dir)
|
|
|
|
|
except Exception:
|
|
|
|
|
return iter(())
|
2025-12-29 17:05:03 -08:00
|
|
|
|
2026-01-01 20:37:27 -08:00
|
|
|
def _generator() -> Iterator[str]:
|
|
|
|
|
for filename in entries:
|
|
|
|
|
if not (filename.endswith(".py") and not filename.startswith("_")
|
|
|
|
|
and filename != "__init__.py"):
|
|
|
|
|
continue
|
|
|
|
|
mod_name = filename[:-3]
|
|
|
|
|
if "_" not in mod_name:
|
|
|
|
|
continue
|
|
|
|
|
yield mod_name
|
2025-12-05 03:42:57 -08:00
|
|
|
|
2026-01-01 20:37:27 -08:00
|
|
|
return _generator()
|
2025-12-05 03:42:57 -08:00
|
|
|
|
|
|
|
|
|
2026-01-01 20:37:27 -08:00
|
|
|
def _load_cmdlet_module(mod_name: str) -> None:
|
2025-12-05 03:42:57 -08:00
|
|
|
try:
|
2025-12-11 12:47:30 -08:00
|
|
|
_import_module(f".{mod_name}", __name__)
|
2026-01-01 20:37:27 -08:00
|
|
|
except Exception as exc:
|
|
|
|
|
print(f"Error importing cmdlet '{mod_name}': {exc}", file=sys.stderr)
|
|
|
|
|
|
2025-12-29 17:05:03 -08:00
|
|
|
|
2026-01-01 20:37:27 -08:00
|
|
|
def _load_root_modules() -> None:
|
|
|
|
|
for root in ("select_cmdlet",):
|
|
|
|
|
try:
|
|
|
|
|
_import_module(root)
|
|
|
|
|
except Exception:
|
|
|
|
|
continue
|
2025-12-05 03:42:57 -08:00
|
|
|
|
2025-12-29 17:05:03 -08:00
|
|
|
|
2026-01-01 20:37:27 -08:00
|
|
|
def _load_helper_modules() -> None:
|
|
|
|
|
try:
|
|
|
|
|
import API.alldebrid as _alldebrid
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
2025-11-25 20:09:33 -08:00
|
|
|
|
2026-01-01 20:37:27 -08:00
|
|
|
def _register_native_commands() -> None:
|
|
|
|
|
try:
|
|
|
|
|
from cmdnat import register_native_commands
|
|
|
|
|
except Exception:
|
|
|
|
|
return
|
2025-11-25 20:09:33 -08:00
|
|
|
try:
|
2026-01-01 20:37:27 -08:00
|
|
|
register_native_commands(REGISTRY)
|
2025-11-25 20:09:33 -08:00
|
|
|
except Exception:
|
2026-01-01 20:37:27 -08:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
2026-01-11 00:39:17 -08:00
|
|
|
def ensure_cmdlet_modules_loaded(force: bool = False) -> None:
|
2026-01-01 20:37:27 -08:00
|
|
|
global _MODULES_LOADED
|
|
|
|
|
|
2026-01-11 00:39:17 -08:00
|
|
|
if _MODULES_LOADED and not force:
|
2026-01-01 20:37:27 -08:00
|
|
|
return
|
|
|
|
|
|
|
|
|
|
for mod_name in _iter_cmdlet_module_names():
|
|
|
|
|
_load_cmdlet_module(mod_name)
|
|
|
|
|
|
|
|
|
|
_load_root_modules()
|
|
|
|
|
_load_helper_modules()
|
|
|
|
|
_register_native_commands()
|
|
|
|
|
_MODULES_LOADED = True
|