import inspect import os import vultron.api import vultron.display import vultron.err class CmdNotFound(vultron.err.Error): def __str__(self): return f"command not found: {self.args[0]}" class FeatureMissing(vultron.err.Error): def __str__(self): return f"feature not yet implemented: {self.args[0]}" class NoApiKey(vultron.err.Error): def __str__(self): return f"no api key provided" class NotEnoughArgs(vultron.err.Error): def __str__(self): name = self.args[0] more = self.args[1] if self.args[1] > 1: return f"sub-command requires {more} more arguments: {name}" else: return f"sub-command requires 1 more argument: {name}" # FIXME: Handle paging class Command: """A nop Vultron command. This feature is not implemented yet.""" FTR_PRFX = "vultron_" def __init__(self, prog, api_key, out): self.prog = prog self.out = out self.needs_api_key = True self.default = "help" self.init() if self.needs_api_key and api_key is None: raise NoApiKey() self.api = vultron.api.Client(api_key) def exec_shortcut(self, shortcut, *args): fn = self.find(shortcut) sig = inspect.signature(fn) if len(args) < len(sig.parameters): name = fn.__name__[len(self.FTR_PRFX):].lower() raise NotEnoughArgs(name, len(sig.parameters) - len(args)) data = fn(*args) # FIXME: Let functions choose the output format. This will allow help # functions to still return a rendering. if data is not None: display = vultron.display.FORMATS[self.out](data) print(display.render()) def find(self, name): needle = f"{self.FTR_PRFX}{name}".lower() haystack = dir(self) for legume in haystack: if legume.lower().startswith(needle): fn = getattr(self, legume) if callable(fn): return fn else: raise FeatureMissing(legume) raise CmdNotFound(name) def init(self): pass def vultron_help(self): """This help message.""" prog = os.path.basename(self.prog) cmd = type(self).__name__.lower() subs = dir(self) print(f"usage: {prog} [options] {cmd} ") print() print("About:") if self.__doc__ is None: print(Command.__doc__) else: print(self.__doc__) print() # FIXME: print global options here print("Sub-commands:") for sub in subs: name = sub.lower() if name.startswith(self.FTR_PRFX): name = name[len(self.FTR_PRFX):] fn = getattr(self, sub) if callable(fn): desc = name if fn.__doc__ is not None: desc += f" - {fn.__doc__}" print(f"\t{desc}") def find_cmd(needle): needle = needle.lower() haystack = Command.__subclasses__() for legume in haystack: if legume.__name__.lower().startswith(needle): return legume raise CmdNotFound(needle)