Skip to content

Instantly share code, notes, and snippets.

@rmorshea
Created January 26, 2023 00:00
Show Gist options
  • Save rmorshea/c6ae34065f80207eba5af6de41e7573c to your computer and use it in GitHub Desktop.
Save rmorshea/c6ae34065f80207eba5af6de41e7573c to your computer and use it in GitHub Desktop.
import sys
from inspect import signature, Signature, Parameter, BoundArguments, cleandoc
from argparse import ArgumentParser, RawTextHelpFormatter
from typing import TypeVar, Callable
F = TypeVar("F", bound=Callable[..., None])
class Commands:
def __init__(self) -> None:
self.parser = ArgumentParser(formatter_class=RawTextHelpFormatter)
self.subparsers = self.parser.add_subparsers()
self.parser.add_argument(
"--debug", action="store_true", dest="_debug", default=False
)
def add(self, func: F) -> F:
sig = signature(func)
parser = self.subparsers.add_parser(
name=func.__name__.replace("_", "-"),
help=cleandoc(func.__doc__),
)
parser.set_defaults(_func=func)
parser.set_defaults(_sig=sig)
for i, param in enumerate(sig.parameters.values()):
name = param.name
flag = f"--{name.replace('_', '-')}"
if param.kind == Parameter.VAR_POSITIONAL:
parser.add_argument(name if i == 0 else flag, nargs="*", default=[])
elif param.kind not in (
Parameter.POSITIONAL_OR_KEYWORD,
Parameter.KEYWORD_ONLY,
):
raise TypeError(f"Unsupported parameter type {param.kind.description}")
elif param.default is not Parameter.empty:
if isinstance(param.default, bool):
parser.add_argument(
flag,
action=f"store_{str(not param.default).lower()}",
default=param.default,
)
else:
parser.add_argument(flag, nargs="?", default=param.default)
else:
parser.add_argument(flag, nargs="?")
def __call__(self):
self.run(*sys.argv[1:])
def run(self, *args: str) -> None:
namespace = self.parser.parse_args(args)
if not (hasattr(namespace, "_func") and hasattr(namespace, "_sig")):
self.parser.print_help()
return
debug: bool = namespace._debug
func: Callable[..., None] = namespace._func
sig: Signature = namespace._sig
bound = BoundArguments(sig, namespace.__dict__)
try:
func(*bound.args, **bound.kwargs)
except Exception as error:
if debug:
raise
print(error)
print("Use --debug to see the a full traceback")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment