diff --git a/tools/python/jwutils/Cmd.py b/tools/python/jwutils/Cmd.py index 86d2ada..6c9fdcd 100644 --- a/tools/python/jwutils/Cmd.py +++ b/tools/python/jwutils/Cmd.py @@ -1,9 +1,14 @@ import abc import argparse +from jwutils import log + # compatible with Python 2 *and* 3 ABC = abc.ABCMeta('ABC', (object,), {'__slots__': ()}) +# full blown example of one level of nested subcommands +# git -C project remote -v show -n myremote + class Cmd(ABC): # export @abc.abstractmethod @@ -13,6 +18,9 @@ class Cmd(ABC): # export def __init__(self, name, help): self.name = name self.help = help + self.parent = None + self.children = [] + self.child_classes = [] async def _run(self, args): pass @@ -23,3 +31,12 @@ class Cmd(ABC): # export r.set_defaults(func=self.run) return r + def add_subcommands(self, cmd): + if isinstance(cmd, list): + for c in cmd: + self.add_subcommands(c) + return + self.child_classes.append(cmd) + + def add_arguments(self, parser): + pass diff --git a/tools/python/jwutils/Cmds.py b/tools/python/jwutils/Cmds.py index 4bad7ca..b2e2cf2 100644 --- a/tools/python/jwutils/Cmds.py +++ b/tools/python/jwutils/Cmds.py @@ -6,15 +6,27 @@ import inspect import re import pickle import asyncio + import jwutils from jwutils import log class Cmds: # export + def __add_cmd_to_parser(self, cmd, parsers): + parser = cmd.add_parser(parsers) + cmd.add_arguments(parser) + if len(cmd.child_classes) > len(cmd.children): + for c in cmd.child_classes: + cmd.children.append(c()) + for sub_cmd in cmd.children: + subparsers = parser.add_subparsers(title='Available commands of ' + cmd.name, metavar='') + self.__add_cmd_to_parser(sub_cmd, subparsers) + def __init__(self, description = '', filter = '^Cmd.*', modules=None, eloop=None): self.__description = description self.__filter = filter self.__modules = modules + self.__cmds = [] default_log_level = log.NOTICE default_log_flags = 'stderr,position,prio,color' # poor man's parsing in the absence of a complete command-line definition @@ -40,9 +52,9 @@ class Cmds: # export if eloop is None: self.eloop = asyncio.get_event_loop() self.__own_eloop = True - subparsers = self.__parser.add_subparsers(title='Available commands', metavar='') if self.__modules == None: self.__modules = [ '__main__' ] + subcmds = set() for m in self.__modules: if m != '__main__': importlib.import_module(m) @@ -54,7 +66,16 @@ class Cmds: # export log.slog(log.DEBUG, 'instantiating command "{}"'.format(c)) cmd = c() cmd.cmds = self - cmd.add_parser(subparsers) + #cmd.add_parser(subparsers) + self.__cmds.append(cmd) + for child in cmd.child_classes: + subcmds.add(child) + subcmds.update(cmd.child_classes) + + cmds = [cmd for cmd in self.__cmds if type(cmd) not in subcmds] + subparsers = self.__parser.add_subparsers(title='Available commands', metavar='') + for cmd in cmds: + self.__add_cmd_to_parser(cmd, subparsers) async def __run(self): args = self.__parser.parse_args() @@ -69,7 +90,7 @@ class Cmds: # export self.eloop = None self.__own_eloop = False - def parser(): + def parser(self): return self.__parser def run(self):