diff --git a/src/python/jw/build/App.py b/src/python/jw/build/App.py index 39d298b5..c1b4f08f 100644 --- a/src/python/jw/build/App.py +++ b/src/python/jw/build/App.py @@ -715,7 +715,7 @@ class App(object): r = r + ':' + pd + '/lib' print(r[1:]) - def cmd_pythonpath(self, args_): + def cmd_pythonpath_orig(self, args_): parser = argparse.ArgumentParser(description='pythonpath') parser.add_argument('module', nargs='*', help='Modules') args = parser.parse_args(args_) @@ -959,6 +959,18 @@ class App(object): args = parser.parse_args(args_) print(self.get_value(args.project, args.section, args.key)) + def run_from_cmd_module(self, name: str, args_) -> None: + import importlib + name = name.replace('-', '_') + cc_name = 'Cmd' + ''.join(x.capitalize() for x in name.lower().split("_")) + module = importlib.import_module('jw.build.cmds.' + cc_name) + cmd = getattr(module, cc_name)() + cmd.app = self + parser = argparse.ArgumentParser(description=name) + cmd.add_arguments(parser) + args = parser.parse_args(args_) + return cmd.run(args) + # -------------------------------------------------------------------- here we go def run(self): @@ -1003,6 +1015,10 @@ class App(object): if not self.top_name: self.top_name = re.sub('-[0-9.-]*$', '', basename(realpath(self.topdir))) + try: + return self.run_from_cmd_module(args.cmd, sys.argv[(len(self.global_args) + 1)::]) + except: + pass cmd_name = 'cmd_' + args.cmd.replace('-', '_') cmd = getattr(self, cmd_name) cmd(sys.argv[(len(self.global_args) + 1)::]) diff --git a/src/python/jw/build/Cmd.py b/src/python/jw/build/Cmd.py new file mode 100644 index 00000000..5f163cf5 --- /dev/null +++ b/src/python/jw/build/Cmd.py @@ -0,0 +1,58 @@ +# -*- 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 diff --git a/src/python/jw/build/cmds/Makefile b/src/python/jw/build/cmds/Makefile new file mode 100644 index 00000000..781b0c8c --- /dev/null +++ b/src/python/jw/build/cmds/Makefile @@ -0,0 +1,4 @@ +TOPDIR = ../../../../.. + +include $(TOPDIR)/make/proj.mk +include $(JWBDIR)/make/py-mod.mk