Implement basic usability and rendering for some commands
This commit is contained in:
parent
4ce3b38ccf
commit
7700a73641
|
@ -1,17 +1,28 @@
|
||||||
|
import os
|
||||||
|
import vultron.api
|
||||||
|
|
||||||
class CmdNotFound(Exception):
|
class CmdNotFound(Exception):
|
||||||
pass
|
def __str__(self):
|
||||||
|
return f"error: command not found: {self.args[0]}"
|
||||||
|
|
||||||
class NoApiKey(Exception):
|
class NoApiKey(Exception):
|
||||||
|
# FIXME: __str__ message
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class FeatureMissing(Exception):
|
class FeatureMissing(Exception):
|
||||||
|
# FIXME: __str__ message
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# FIXME: Move command execution into Command class, so wrapper can do pre/post
|
||||||
|
# work.
|
||||||
class Command:
|
class Command:
|
||||||
|
"""A nop Vultron command. This feature is not implemented yet."""
|
||||||
|
|
||||||
FTR_PRFX = "vultron_"
|
FTR_PRFX = "vultron_"
|
||||||
|
|
||||||
def __init__(self, cmd, api_key):
|
def __init__(self, prog, api_key, out):
|
||||||
self.cmd = cmd
|
self.prog = prog
|
||||||
|
self.out = out
|
||||||
self.uses_api = True
|
self.uses_api = True
|
||||||
self.default = "help"
|
self.default = "help"
|
||||||
|
|
||||||
|
@ -20,11 +31,13 @@ class Command:
|
||||||
if self.uses_api and api_key is None:
|
if self.uses_api and api_key is None:
|
||||||
raise NoApiKey()
|
raise NoApiKey()
|
||||||
|
|
||||||
|
self.api = vultron.api.Client(api_key)
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def find(self, name):
|
def find(self, name):
|
||||||
needle = "{prfx}{name}".format(prfx=self.FTR_PRFX, name=name.lower())
|
needle = f"{self.FTR_PRFX}{name}".lower()
|
||||||
haystack = dir(self)
|
haystack = dir(self)
|
||||||
|
|
||||||
for legume in haystack:
|
for legume in haystack:
|
||||||
|
@ -39,7 +52,39 @@ class Command:
|
||||||
raise CmdNotFound(name)
|
raise CmdNotFound(name)
|
||||||
|
|
||||||
def vultron_help(self, *args):
|
def vultron_help(self, *args):
|
||||||
pass
|
"""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):
|
def find_cmd(needle):
|
||||||
needle = needle.lower()
|
needle = needle.lower()
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
# FIXME: Make this whole package less fragile
|
||||||
|
TYPES = ["tbl", "chrt", "csv", "json"]
|
||||||
|
|
||||||
|
def wall(len, fill="\u2501"):
|
||||||
|
return fill * len
|
||||||
|
|
||||||
|
# FIXME: normalize/analyze table data before building for optimization
|
||||||
|
def table(data):
|
||||||
|
lens = dict()
|
||||||
|
|
||||||
|
for row in data:
|
||||||
|
for col in row:
|
||||||
|
col_len = len(col)
|
||||||
|
val_len = len(str(row[col]))
|
||||||
|
max_len = 0
|
||||||
|
|
||||||
|
if col in lens:
|
||||||
|
max_len = lens[col]
|
||||||
|
|
||||||
|
lens[col] = max(col_len, val_len, max_len)
|
||||||
|
|
||||||
|
col_hdrs = [{"column": col_hdr, "length": lens[col_hdr]} for col_hdr in lens]
|
||||||
|
out = f"\u250F{wall(col_hdrs[0]['length'] + 2)}"
|
||||||
|
|
||||||
|
for col_hdr in col_hdrs[1:]:
|
||||||
|
out += f"\u2533{wall(col_hdr['length'] + 2)}"
|
||||||
|
|
||||||
|
out += "\u2513\n"
|
||||||
|
|
||||||
|
for col_hdr in col_hdrs:
|
||||||
|
out += f"\u2503 {col_hdr['column'].ljust(col_hdr['length'])} "
|
||||||
|
|
||||||
|
out += f"\u2503\n\u2523{wall(col_hdrs[0]['length'] + 2)}"
|
||||||
|
|
||||||
|
for col_hdr in col_hdrs[1:]:
|
||||||
|
out += f"\u254B{wall(col_hdr['length'] + 2)}"
|
||||||
|
|
||||||
|
out += "\u252B\n"
|
||||||
|
|
||||||
|
for row in data[:-1]:
|
||||||
|
for col_hdr in col_hdrs:
|
||||||
|
out += "\u2503 "
|
||||||
|
|
||||||
|
if col_hdr["column"] in row:
|
||||||
|
out += str(row[col_hdr["column"]]).ljust(col_hdr["length"])
|
||||||
|
else:
|
||||||
|
out += " " * col_hdr["length"]
|
||||||
|
|
||||||
|
out += " "
|
||||||
|
|
||||||
|
out += f"\u2503\n\u2523{wall(col_hdrs[0]['length'] + 2)}"
|
||||||
|
|
||||||
|
for col_hdr in col_hdrs[1:]:
|
||||||
|
out += f"\u254B{wall(col_hdr['length'] + 2)}"
|
||||||
|
|
||||||
|
out += "\u252B\n"
|
||||||
|
|
||||||
|
for col_hdr in col_hdrs:
|
||||||
|
out += "\u2503 "
|
||||||
|
|
||||||
|
if col_hdr["column"] in data[-1]:
|
||||||
|
out += str(data[-1][col_hdr["column"]]).ljust(col_hdr["length"])
|
||||||
|
else:
|
||||||
|
out += " " * col_hdr["length"]
|
||||||
|
|
||||||
|
out += " "
|
||||||
|
|
||||||
|
out += "\u2503\n"
|
||||||
|
|
||||||
|
out += f"\u2517{wall(col_hdrs[0]['length'] + 2)}"
|
||||||
|
|
||||||
|
for col_hdr in col_hdrs[1:]:
|
||||||
|
out += f"\u253B{wall(col_hdr['length'] + 2)}"
|
||||||
|
|
||||||
|
out += "\u251B"
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
def chrt(data):
|
||||||
|
return data
|
||||||
|
|
||||||
|
def csv(data):
|
||||||
|
return data
|
||||||
|
|
||||||
|
def json(data):
|
||||||
|
return data
|
||||||
|
|
||||||
|
def render(out, data):
|
||||||
|
# FIXME: option to pipe into pager, cut off at max column, squeeze, or do
|
||||||
|
# nothing (depends on TTY/PAGER)
|
||||||
|
if out == "tbl":
|
||||||
|
return table(data)
|
||||||
|
elif out == "chrt":
|
||||||
|
return chart(data)
|
||||||
|
elif out == "csv":
|
||||||
|
return csv(data)
|
||||||
|
elif out == "json":
|
||||||
|
return json(data)
|
||||||
|
# FIXME: error unrecognized out format
|
|
@ -2,26 +2,54 @@ import click
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import vultron.cmd
|
import vultron.cmd
|
||||||
|
import vultron.display
|
||||||
|
|
||||||
class Help(vultron.cmd.Command):
|
class Help(vultron.cmd.Command):
|
||||||
def init(self):
|
def init(self):
|
||||||
self.uses_api = False
|
self.uses_api = False
|
||||||
|
|
||||||
def vultron_help(self, *args):
|
def vultron_help(self, *args):
|
||||||
|
# FIXME: print global help message
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class Account(vultron.cmd.Command):
|
class Account(vultron.cmd.Command):
|
||||||
def init(self):
|
"""Query for account information."""
|
||||||
self.default = "info"
|
|
||||||
|
|
||||||
def vultron_info(self, *args):
|
def vultron_info(self, *args):
|
||||||
pass
|
"""Display your account details."""
|
||||||
|
|
||||||
|
account = self.api.get("account")
|
||||||
|
display = vultron.display.render(self.out, [account["account"]])
|
||||||
|
|
||||||
|
print(display)
|
||||||
|
|
||||||
|
class VPC(vultron.cmd.Command):
|
||||||
|
"""Query for information on VPCs."""
|
||||||
|
|
||||||
|
def vultron_list(self, *args):
|
||||||
|
"""Display all VPCs."""
|
||||||
|
|
||||||
|
vpcs = self.api.get("vpcs")
|
||||||
|
display = vultron.display.render(self.out, vpcs["vpcs"])
|
||||||
|
|
||||||
|
print(display)
|
||||||
|
|
||||||
|
class Region(vultron.cmd.Command):
|
||||||
|
"""Query for information on Vultr regions."""
|
||||||
|
|
||||||
|
def vultron_list(self, *args):
|
||||||
|
regions = self.api.get("regions")
|
||||||
|
display = vultron.display.render(self.out, regions["regions"])
|
||||||
|
|
||||||
|
print(display)
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option("--help", is_flag=True)
|
@click.option("--help", is_flag=True)
|
||||||
@click.option("--api-key", type=str)
|
@click.option("--api-key", type=str)
|
||||||
|
@click.option("--out", type=click.Choice(vultron.display.TYPES), default="tbl")
|
||||||
@click.argument("args", nargs=-1)
|
@click.argument("args", nargs=-1)
|
||||||
def app(help, api_key, args):
|
# FIXME: click should not show any help messages
|
||||||
|
def app(help, api_key, out, args):
|
||||||
env_key = os.getenv("VULTR_API_KEY")
|
env_key = os.getenv("VULTR_API_KEY")
|
||||||
|
|
||||||
if api_key is None and env_key:
|
if api_key is None and env_key:
|
||||||
|
@ -35,7 +63,7 @@ def app(help, api_key, args):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
vultron_cmd = vultron.cmd.find_cmd(shortcut)
|
vultron_cmd = vultron.cmd.find_cmd(shortcut)
|
||||||
vultron_api = vultron_cmd(sys.argv[0], api_key=api_key)
|
vultron_api = vultron_cmd(sys.argv[0], api_key=api_key, out=out)
|
||||||
|
|
||||||
if len(args):
|
if len(args):
|
||||||
shortcut = args[0]
|
shortcut = args[0]
|
||||||
|
@ -46,4 +74,6 @@ def app(help, api_key, args):
|
||||||
vultron_fn = vultron_api.find(shortcut)
|
vultron_fn = vultron_api.find(shortcut)
|
||||||
vultron_fn(*args)
|
vultron_fn(*args)
|
||||||
except vultron.cmd.CmdNotFound as err:
|
except vultron.cmd.CmdNotFound as err:
|
||||||
print(err)
|
# FIXME: Base Vultron error class
|
||||||
|
print(err, file=sys.stderr)
|
||||||
|
# FIXME: Activate help command
|
||||||
|
|
Loading…
Reference in New Issue