Refactor CLI class: enhance type hints, improve help handling, and streamline argument parsing

This commit is contained in:
Chipperfluff 2026-04-03 14:37:53 +02:00
parent 37eaee3dbe
commit e34136305c

View File

@ -1,44 +1,12 @@
import argparse import argparse
from typing import Any, Callable, Iterable, Optional
from colorama import Fore, Style, init from colorama import Fore, Style, init
init(autoreset=True) init(autoreset=True)
class CLI: _INTERNAL_ARG_FIELDS = frozenset({"command", "func", "_args", "help"})
def __init__(self, prog="nut", help_callback=None): _COLOR_TOKEN_MAP = {
self.parser = argparse.ArgumentParser(prog=prog, add_help=False)
self.subparsers = self.parser.add_subparsers(dest="command")
self.help_callback = help_callback
self.parser.add_argument("-h", "--help", action="store_true")
def command(self, name):
parser = self.subparsers.add_parser(name, add_help=False)
cmd = _Command(parser)
return cmd
def run(self):
args = self.parser.parse_args()
if getattr(args, "help", False) or not getattr(args, "command", None):
if self.help_callback:
for line in self.help_callback():
print(self._color(line))
else:
self.parser.print_help()
return 0
args_list = getattr(args, "_args", [])
flags = {
k: v for k, v in vars(args).items()
if k not in ("command", "func", "_args", "help")
}
return args.func(args_list, flags)
def _color(self, text: str) -> str:
mapping = {
"%red%": Fore.RED, "%red%": Fore.RED,
"%green%": Fore.GREEN, "%green%": Fore.GREEN,
"%yellow%": Fore.YELLOW, "%yellow%": Fore.YELLOW,
@ -50,35 +18,88 @@ class CLI:
"%bold%": Style.BRIGHT, "%bold%": Style.BRIGHT,
} }
for key, value in mapping.items():
class CLI:
def __init__(
self,
prog: str = "nut",
help_callback: Optional[Callable[[], Iterable[str]]] = None,
):
self.parser = argparse.ArgumentParser(prog=prog, add_help=False)
self.subparsers = self.parser.add_subparsers(dest="command")
self.help_callback = help_callback
self.parser.add_argument("-h", "--help", action="store_true")
def command(self, name: str) -> "_Command":
command_parser = self.subparsers.add_parser(name, add_help=False)
return _Command(command_parser)
def run(self) -> int:
parsed_args = self.parser.parse_args()
if self._should_show_help(parsed_args):
self._print_help()
return 0
handler = getattr(parsed_args, "func", None)
if handler is None:
raise RuntimeError("No handler defined")
args_list = list(getattr(parsed_args, "_args", []))
flags = {
k: v for k, v in vars(parsed_args).items()
if k not in _INTERNAL_ARG_FIELDS
}
return handler(args_list, flags)
def _should_show_help(self, parsed_args: argparse.Namespace) -> bool:
return bool(
getattr(parsed_args, "help", False)
or not getattr(parsed_args, "command", None)
)
def _print_help(self) -> None:
if self.help_callback is None:
self.parser.print_help()
return
for line in self.help_callback():
print(self._color(str(line)))
def _color(self, text: str) -> str:
for key, value in _COLOR_TOKEN_MAP.items():
text = text.replace(key, value) text = text.replace(key, value)
return text return text
class _Command: class _Command:
def __init__(self, parser): def __init__(self, parser: argparse.ArgumentParser):
self.parser = parser self.parser = parser
self.parser.set_defaults(func=self._missing) self.parser.set_defaults(func=self._missing_handler)
self.parser.add_argument("_args", nargs="*") self.parser.add_argument("_args", nargs="*")
self.parser.add_argument("-h", "--help", action="store_true") self.parser.add_argument("-h", "--help", action="store_true")
def arg(self, name, **kwargs): def arg(self, name: str, **kwargs: Any) -> "_Command":
self.parser.add_argument(name, **kwargs) self.parser.add_argument(name, **kwargs)
return self return self
def flag(self, name, short=None): def flag(self, name: str, short: Optional[str] = None) -> "_Command":
opts = [f"--{name}"] opts = [f"--{name}"]
if short: if short:
opts.append(f"-{short}") opts.append(f"-{short.lstrip('-')}")
self.parser.add_argument(*opts, action="store_true") self.parser.add_argument(*opts, action="store_true")
return self return self
def handle(self, func): def handle(self, func: Callable[..., int]) -> "_Command":
self.parser.set_defaults(func=func) self.parser.set_defaults(func=func)
return self return self
def _missing(self, *_): def _missing_handler(self, *_: Any) -> int:
raise RuntimeError("No handler defined") raise RuntimeError("No handler defined")