Files
Medios-Macina/SYS/cmdlet_catalog.py

292 lines
9.7 KiB
Python
Raw Permalink Normal View History

2025-12-29 19:00:00 -08:00
from __future__ import annotations
from importlib import import_module
2026-01-01 20:37:27 -08:00
from types import ModuleType
2025-12-29 19:00:00 -08:00
from typing import Any, Dict, List, Optional
try:
from .config import get_local_storage_path
except Exception:
get_local_storage_path = None # type: ignore
def _should_hide_db_args(config: Optional[Dict[str, Any]]) -> bool:
"""Return True when the library root/local DB is not configured."""
if not isinstance(config, dict):
return False
if get_local_storage_path is None:
return False
try:
return not bool(get_local_storage_path(config))
except Exception:
return False
2026-01-01 20:37:27 -08:00
_cmdlet_pkg: ModuleType | None = None
2025-12-29 19:00:00 -08:00
2026-01-01 20:37:27 -08:00
def _get_cmdlet_package() -> Optional[ModuleType]:
global _cmdlet_pkg
if _cmdlet_pkg is not None:
return _cmdlet_pkg
try:
_cmdlet_pkg = import_module("cmdlet")
except Exception:
_cmdlet_pkg = None
return _cmdlet_pkg
def _get_registry() -> Dict[str, Any]:
pkg = _get_cmdlet_package()
if pkg is None:
return {}
return getattr(pkg, "REGISTRY", {}) or {}
2025-12-29 19:00:00 -08:00
2026-01-11 00:39:17 -08:00
def ensure_registry_loaded(force: bool = False) -> None:
"""Ensure native commands are registered into REGISTRY (idempotent unless force=True)."""
2026-01-01 20:37:27 -08:00
pkg = _get_cmdlet_package()
if pkg is None:
return
ensure_fn = getattr(pkg, "ensure_cmdlet_modules_loaded", None)
if callable(ensure_fn):
2025-12-29 19:00:00 -08:00
try:
2026-01-11 00:39:17 -08:00
ensure_fn(force=force)
2025-12-29 19:00:00 -08:00
except Exception:
pass
def _normalize_mod_name(mod_name: str) -> str:
"""Normalize a command/module name for import resolution."""
normalized = (mod_name or "").strip()
if normalized.startswith("."):
normalized = normalized.lstrip(".")
normalized = normalized.replace("-", "_")
return normalized
def import_cmd_module(mod_name: str):
"""Import a cmdlet/native module from cmdnat or cmdlet packages."""
normalized = _normalize_mod_name(mod_name)
if not normalized:
return None
for package in ("cmdnat", "cmdlet", None):
try:
qualified = f"{package}.{normalized}" if package else normalized
return import_module(qualified)
except ModuleNotFoundError:
continue
except Exception:
continue
return None
def _normalize_arg(arg: Any) -> Dict[str, Any]:
"""Convert a CmdletArg/dict into a plain metadata dict."""
if isinstance(arg, dict):
name = arg.get("name", "")
return {
"name": str(name).lstrip("-"),
"type": arg.get("type", "string"),
"required": bool(arg.get("required", False)),
"description": arg.get("description", ""),
"choices": arg.get("choices", []) or [],
"alias": arg.get("alias", ""),
"variadic": arg.get("variadic", False),
"requires_db": bool(arg.get("requires_db", False)),
}
name = getattr(arg, "name", "") or ""
return {
"name": str(name).lstrip("-"),
"type": getattr(arg, "type", "string"),
"required": bool(getattr(arg, "required", False)),
"description": getattr(arg, "description", ""),
"choices": getattr(arg, "choices", []) or [],
"alias": getattr(arg, "alias", ""),
"variadic": getattr(arg, "variadic", False),
"requires_db": bool(getattr(arg, "requires_db", False)),
}
def get_cmdlet_metadata(
cmd_name: str, config: Optional[Dict[str, Any]] = None
) -> Optional[Dict[str, Any]]:
"""Return normalized metadata for a cmdlet, if available (aliases supported)."""
ensure_registry_loaded()
normalized = cmd_name.replace("-", "_")
mod = import_cmd_module(normalized)
data = getattr(mod, "CMDLET", None) if mod else None
if data is None:
try:
2026-01-01 20:37:27 -08:00
registry = _get_registry()
reg_fn = registry.get(cmd_name.replace("_", "-").lower())
2025-12-29 19:00:00 -08:00
if reg_fn:
owner_mod = getattr(reg_fn, "__module__", "")
if owner_mod:
owner = import_module(owner_mod)
data = getattr(owner, "CMDLET", None)
except Exception:
data = None
if not data:
return None
if hasattr(data, "to_dict"):
base = data.to_dict()
elif isinstance(data, dict):
base = data
else:
base = {}
name = getattr(data, "name", base.get("name", cmd_name)) or cmd_name
aliases = getattr(data, "alias", base.get("alias", [])) or []
usage = getattr(data, "usage", base.get("usage", ""))
summary = getattr(data, "summary", base.get("summary", ""))
details = getattr(data, "detail", base.get("detail", [])) or []
args_list = getattr(data, "arg", base.get("arg", [])) or []
args = [_normalize_arg(arg) for arg in args_list]
2026-01-03 03:37:48 -08:00
examples_list = getattr(data, "examples", base.get("examples", [])) or []
if not examples_list:
examples_list = getattr(data, "example", base.get("example", [])) or []
examples = []
for example in examples_list:
text = str(example or "").strip()
if text:
examples.append(text)
2025-12-29 19:00:00 -08:00
if _should_hide_db_args(config):
args = [a for a in args if not a.get("requires_db")]
return {
"name": str(name).replace("_", "-").lower(),
"aliases": [str(a).replace("_", "-").lower() for a in aliases if a],
"usage": usage,
"summary": summary,
"details": details,
"args": args,
2026-01-03 03:37:48 -08:00
"examples": examples,
2025-12-29 19:00:00 -08:00
"raw": data,
}
2026-01-11 00:39:17 -08:00
def list_cmdlet_metadata(
force: bool = False, config: Optional[Dict[str, Any]] = None
) -> Dict[str, Dict[str, Any]]:
2025-12-29 19:00:00 -08:00
"""Collect metadata for all registered cmdlet keyed by canonical name."""
2026-01-11 00:39:17 -08:00
ensure_registry_loaded(force=force)
2025-12-29 19:00:00 -08:00
entries: Dict[str, Dict[str, Any]] = {}
2026-01-01 20:37:27 -08:00
registry = _get_registry()
for reg_name in registry.keys():
2025-12-29 19:00:00 -08:00
meta = get_cmdlet_metadata(reg_name, config=config)
canonical = str(reg_name).replace("_", "-").lower()
if meta:
canonical = meta.get("name", canonical)
aliases = meta.get("aliases", [])
base = entries.get(
canonical,
{
"name": canonical,
"aliases": [],
"usage": "",
"summary": "",
"details": [],
"args": [],
2026-01-03 03:37:48 -08:00
"examples": meta.get("examples", []),
2025-12-29 19:00:00 -08:00
"raw": meta.get("raw"),
},
)
merged_aliases = set(base.get("aliases", [])) | set(aliases)
if canonical != reg_name:
merged_aliases.add(reg_name)
base["aliases"] = sorted(a for a in merged_aliases if a and a != canonical)
if not base.get("usage") and meta.get("usage"):
base["usage"] = meta["usage"]
if not base.get("summary") and meta.get("summary"):
base["summary"] = meta["summary"]
if not base.get("details") and meta.get("details"):
base["details"] = meta["details"]
if not base.get("args") and meta.get("args"):
base["args"] = meta["args"]
2026-01-03 03:37:48 -08:00
example_sources: List[str] = []
for attr in ("examples", "example"):
values = meta.get(attr, []) if isinstance(meta, dict) else []
example_sources.extend(values or [])
merged_examples = [e for e in base.get("examples", []) or []]
for example_entry in example_sources:
if example_entry not in merged_examples:
merged_examples.append(example_entry)
base["examples"] = merged_examples
2025-12-29 19:00:00 -08:00
if not base.get("raw"):
base["raw"] = meta.get("raw")
entries[canonical] = base
else:
entries.setdefault(
canonical,
{
"name": canonical,
"aliases": [],
"usage": "",
"summary": "",
"details": [],
"args": [],
2026-01-03 03:37:48 -08:00
"examples": [],
2025-12-29 19:00:00 -08:00
"raw": None,
},
)
return entries
def list_cmdlet_names(
2026-01-11 00:39:17 -08:00
include_aliases: bool = True,
force: bool = False,
config: Optional[Dict[str, Any]] = None,
2025-12-29 19:00:00 -08:00
) -> List[str]:
"""Return sorted cmdlet names (optionally including aliases)."""
2026-01-11 00:39:17 -08:00
ensure_registry_loaded(force=force)
entries = list_cmdlet_metadata(force=force, config=config)
2025-12-29 19:00:00 -08:00
names = set()
for meta in entries.values():
names.add(meta.get("name", ""))
if include_aliases:
for alias in meta.get("aliases", []):
names.add(alias)
return sorted(n for n in names if n)
def get_cmdlet_arg_flags(cmd_name: str, config: Optional[Dict[str, Any]] = None) -> List[str]:
"""Return flag variants for cmdlet arguments (e.g., -name/--name)."""
meta = get_cmdlet_metadata(cmd_name, config=config)
if not meta:
return []
flags: List[str] = []
seen: set[str] = set()
for arg in meta.get("args", []):
name = str(arg.get("name") or "").strip().lstrip("-")
if not name:
continue
for candidate in (f"-{name}", f"--{name}"):
if candidate not in seen:
flags.append(candidate)
seen.add(candidate)
return flags
def get_cmdlet_arg_choices(
cmd_name: str, arg_name: str, config: Optional[Dict[str, Any]] = None
) -> List[str]:
"""Return declared choices for a cmdlet argument."""
meta = get_cmdlet_metadata(cmd_name, config=config)
if not meta:
return []
target = arg_name.lstrip("-")
for arg in meta.get("args", []):
if arg.get("name") == target:
return list(arg.get("choices", []) or [])
return []