Source code for cmdcraft.command

#!/usr/bin/env python3
"""Callable wrapper for info extraction."""

from __future__ import annotations

import asyncio
import inspect
from typing import get_type_hints

from .parameter import Parameter


[docs] class Command: """Command wrapper. This class handles commands / callables to extract information, specially its parameters. """ def __init__(self, cb: callable, alias: str | None = None) -> None: """Construct a Command object. Args: cb (callable): Callable to be wrapped. alias (str | None, optional): Command name. Defaults to None. """ self._cb: callable = cb self._pars: dict[str, Parameter] = {} # Remove later on self._positional: dict[str, Parameter] = {} self._keyword: dict[str, Parameter] = {} self._name: str = cb.__name__ self._alias: str = alias if alias is not None else self.name self._has_args: bool = False self._has_kwargs: bool = False @property def __doc__(self) -> str: """Return the original callable docstring. Returns: str: Command docstring. """ d = self._cb.__doc__ fb = "" return d if d is not None else fb @property def name(self) -> str: """Return the command name. Returns: str: Command name. """ return self._name @property def alias(self) -> str: """Return the command alias. Returns: str: Command alias. """ return self._alias @property def parameters(self) -> dict[str, Parameter]: """Return a dictionary of all parameters. Returns: dict[str, Parameter]: Parameters. """ return self._pars @property def positional_parameters(self) -> dict[str, Parameter]: """Return a dictionary of positional parameters. Returns: dict[str, Parameter]: Parameters. """ return self._positional @property def keyword_parameters(self) -> dict[str, Parameter]: """Return a dictionary of positional parameters. Returns: dict[str, Parameter]: Parameters. """ return self._keyword @property def has_args(self) -> bool: """Return if the command accepts variadic non-keyword arguments.""" return self._has_args @property def has_kwargs(self) -> bool: """Return if the command accepts variadic keyword arguments.""" return self._has_kwargs
[docs] def eval(self, *args) -> asyncio.Future: """Evaluate a call. Returns: asyncio.Future: A future of this callable. """ pos: list[str] = [x for x in args if "--" not in x] kws: list[str] = [x.lstrip("--") for x in args if "--" in x] args = [] var_args = [] for a, p in zip(pos, self._positional.values()): args.append(p.cast(a)) if self.has_args: var_args = pos[len(self._positional):] kwargs = {} for kw in kws: [par, value] = kw.split("=") if par in self._pars: kwargs[par] = self._pars[par].cast(value) elif self.has_kwargs: kwargs[par] = value return self._cb(*args, *var_args, **kwargs)
[docs] def process(self) -> None: """Process the callable metadata.""" f = self._cb anns = get_type_hints(f) pars = inspect.signature(f).parameters for k, v in pars.items(): default = None ptype = None if v.default is not inspect.Parameter.empty: default = v.default if k in anns: ptype = anns[k] par = Parameter(k, ptype, default) if v.kind in (v.POSITIONAL_ONLY, v.POSITIONAL_OR_KEYWORD): self._positional[k] = par elif v.kind == v.KEYWORD_ONLY: self._keyword[k] = par elif v.kind == v.VAR_POSITIONAL: self._has_args = True elif v.kind == v.VAR_KEYWORD: self._has_kwargs = True self._pars[k] = par
[docs] def parameter(self, parameter: str) -> Parameter | None: """Parameter getter.""" return self._pars.get(parameter, None)