# -*- coding: utf-8 -*- from __future__ import annotations from typing import Type, Union, TypeVar import inspect, abc, argparse from argparse import ArgumentParser class Cmd(abc.ABC): # export def __init__(self, name: str, help: str) -> None: from .App import App self.name = name self.help = help self.parent = None self.children: list[Cmd] = [] self.child_classes: list[Type[Cmd]] = [] self.app: App|None = None @abc.abstractmethod def _run(self, args): pass def run(self, args): return self._run(args) 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): import sys, re 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