Source code for crazyhusk.cli
"""Expose crazyhusk functionality to the commandline."""
# Standard Library
import argparse
import inspect
import logging
import sys
from typing import Any, List
try:
# Standard Library
from importlib.metadata import entry_points # type:ignore
except ImportError:
# Third Party
from importlib_metadata import entry_points # type:ignore
[docs]class CommandError(Exception):
"""Custom exception representing errors encountered with CLI."""
[docs]def set_subcommand_arguments(
parser: argparse.ArgumentParser, command: Any
) -> argparse.ArgumentParser:
"""Dynamically set argparse.Parser subcommand arguments by inspecting a callable function."""
if not isinstance(parser, argparse.ArgumentParser):
raise TypeError(
f"Parser provided must be of type argparse.ArgumentParser. Got: {parser!r}"
)
if not inspect.isfunction(command):
raise TypeError(
f"Command provided must be a callable function. Got:{command!r}"
)
for param in inspect.signature(command).parameters.values():
vargs = []
kwargs = {}
if param.default is not inspect.Parameter.empty:
kwargs["default"] = param.default
name = param.name
if param.kind == inspect.Parameter.POSITIONAL_ONLY:
pass
elif param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
if param.default is not inspect.Parameter.empty:
name = f"--{param.name}"
kwargs["dest"] = param.name
elif param.kind == inspect.Parameter.VAR_POSITIONAL:
kwargs["nargs"] = argparse.ZERO_OR_MORE
elif param.kind == inspect.Parameter.KEYWORD_ONLY:
if param.default is not inspect.Parameter.empty:
name = f"--{param.name}"
kwargs["dest"] = param.name
elif param.kind == inspect.Parameter.VAR_KEYWORD:
pass
else:
raise TypeError(f"Invalid function signature parameter type: {param.kind}")
if type(param.default) is bool:
if param.default:
name = f"--disable-{param.name}"
kwargs["dest"] = param.name
kwargs["action"] = "store_false"
else:
name = f"--enable-{param.name}"
kwargs["dest"] = param.name
kwargs["action"] = "store_true"
vargs.append(name)
parser.add_argument(*vargs, **kwargs)
return parser
[docs]def parse_cli_args(args: List[str]) -> argparse.Namespace:
"""Parse crazyhusk CLI arguments."""
if args is None:
raise CommandError("None is not parsable arguments.")
elif len(args) == 0:
raise CommandError("Must provide at least one argument.")
parser = argparse.ArgumentParser()
commands_parser = parser.add_subparsers(title="subcommands")
for entry_point in entry_points().get("crazyhusk.commands", []):
command = entry_point.load()
if inspect.isfunction(command):
docstring = inspect.getdoc(command)
if docstring is not None:
docstring = docstring.split("\n")[0]
cmd_parser = commands_parser.add_parser(entry_point.name, help=docstring)
cmd_parser.set_defaults(command=command)
set_subcommand_arguments(cmd_parser, command)
return parser.parse_args(args)
[docs]def run(args: List[str] = sys.argv[1:]) -> None:
"""Run the crazyhusk CLI entrypoint."""
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
command_args = parse_cli_args(args)
if "command" in command_args:
vargs = []
kwargs = {}
for param in inspect.signature(command_args.command).parameters.values():
if param.kind == inspect.Parameter.POSITIONAL_ONLY:
vargs.append(getattr(command_args, param.name))
elif param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
vargs.append(getattr(command_args, param.name))
elif param.kind == inspect.Parameter.VAR_POSITIONAL:
vargs.extend(getattr(command_args, param.name))
elif param.kind == inspect.Parameter.KEYWORD_ONLY:
kwargs[param.name] = getattr(command_args, param.name)
elif param.kind == inspect.Parameter.VAR_KEYWORD:
pass
else:
raise TypeError(
f"Invalid function signature parameter type: {param.kind}"
)
command_args.command(*vargs, **kwargs)