# -*- coding: utf-8 -*- from __future__ import annotations from typing import List, Type, Union, TypeVar import inspect, sys, re, abc, argparse from argparse import ArgumentParser, _SubParsersAction from jwutils import log # full blown example of one level of nested subcommands # git -C project remote -v show -n myremote class Cmd(abc.ABC): # export @abc.abstractmethod async def run(self, args): pass def __init__(self, name: str, help: str) -> None: self.name = name self.help = help self.parent = None self.children: List[Cmd] = [] self.child_classes: List[Type[Cmd]] = [] self.app: Optional[Cmds] = None async def _run(self, args): pass def add_parser(self, parsers) -> ArgumentParser: r = parsers.add_parser(self.name, help=self.help, formatter_class=argparse.ArgumentDefaultsHelpFormatter) r.set_defaults(func=self.run) return r def add_subcommands(self, cmd: Union[str, Type[Cmd], List[Type[Cmd]]]) -> None: if isinstance(cmd, str): sc = [] for name, obj in inspect.getmembers(sys.modules[self.__class__.__module__]): if inspect.isclass(obj): if re.search(cmd, str(obj)): sc.append(obj) log.slog(log.DEBUG, f"Found subcommand {obj}") self.add_subcommands(sc) return if isinstance(cmd, list): for c in cmd: self.add_subcommands(c) return self.child_classes.append(cmd) # To be overridden by derived class in case the command does take arguments. # Will be called from App base class constructor and set up the parser hierarchy def add_arguments(self, parser: ArgumentParser) -> None: pass def conf_value(self, path, default=None): ret = None if self.app is None else self.app.conf_value(path, default) if ret is None and default is not None: return default return ret