vultron/vultron/cmd.py

126 lines
2.8 KiB
Python

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} <sub-command>")
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)