157 lines
3.7 KiB
Python
157 lines
3.7 KiB
Python
import inspect
|
|
import sys
|
|
|
|
from typing import Optional, Union, Callable
|
|
from dataclasses import dataclass
|
|
|
|
from . import const, vt100
|
|
|
|
Value = Union[str, bool, int]
|
|
|
|
|
|
class Args:
|
|
opts: dict[str, Value]
|
|
args: list[str]
|
|
|
|
def __init__(self):
|
|
self.opts = {}
|
|
self.args = []
|
|
|
|
def consumePrefix(self, prefix: str) -> dict[str, Value]:
|
|
result: dict[str, Value] = {}
|
|
copy = self.opts.copy()
|
|
for key, value in copy.items():
|
|
if key.startswith(prefix):
|
|
result[key[len(prefix) :]] = value
|
|
del self.opts[key]
|
|
return result
|
|
|
|
def consumeOpt(self, key: str, default: Value = False) -> Value:
|
|
if key in self.opts:
|
|
result = self.opts[key]
|
|
del self.opts[key]
|
|
return result
|
|
return default
|
|
|
|
def tryConsumeOpt(self, key: str) -> Optional[Value]:
|
|
if key in self.opts:
|
|
result = self.opts[key]
|
|
del self.opts[key]
|
|
return result
|
|
return None
|
|
|
|
def consumeArg(self, default: Optional[str] = None) -> Optional[str]:
|
|
if len(self.args) == 0:
|
|
return default
|
|
|
|
first = self.args[0]
|
|
del self.args[0]
|
|
return first
|
|
|
|
|
|
def parse(args: list[str]) -> Args:
|
|
result = Args()
|
|
|
|
for arg in args:
|
|
if arg.startswith("--"):
|
|
if "=" in arg:
|
|
key, value = arg[2:].split("=", 1)
|
|
result.opts[key] = value
|
|
else:
|
|
result.opts[arg[2:]] = True
|
|
else:
|
|
result.args.append(arg)
|
|
|
|
return result
|
|
|
|
|
|
Callback = Callable[[Args], None]
|
|
|
|
|
|
@dataclass
|
|
class Command:
|
|
shortName: Optional[str]
|
|
longName: str
|
|
helpText: str
|
|
isPlugin: bool
|
|
callback: Callback
|
|
|
|
|
|
commands: list[Command] = []
|
|
|
|
|
|
def append(command: Command):
|
|
command.isPlugin = True
|
|
commands.append(command)
|
|
commands.sort(key=lambda c: c.shortName or c.longName)
|
|
|
|
|
|
def command(shortName: Optional[str], longName: str, helpText: str):
|
|
curframe = inspect.currentframe()
|
|
calframe = inspect.getouterframes(curframe, 2)
|
|
|
|
def wrap(fn: Callable[[Args], None]):
|
|
commands.append(
|
|
Command(shortName, longName, helpText, calframe[1].filename != __file__, fn)
|
|
)
|
|
return fn
|
|
|
|
return wrap
|
|
|
|
|
|
# --- Builtins Commands ------------------------------------------------------ #
|
|
|
|
|
|
@command("u", "usage", "Show usage information")
|
|
def usage(args: Optional[Args] = None):
|
|
print(f"Usage: {const.ARGV0} <command> [args...]")
|
|
|
|
|
|
def error(msg: str) -> None:
|
|
print(f"{vt100.RED}Error:{vt100.RESET} {msg}\n", file=sys.stderr)
|
|
|
|
|
|
@command("h", "help", "Show this help message")
|
|
def helpCmd(args: Args):
|
|
usage()
|
|
|
|
print()
|
|
|
|
vt100.title("Description")
|
|
print(f" {const.DESCRIPTION}")
|
|
|
|
print()
|
|
vt100.title("Commands")
|
|
for cmd in sorted(commands, key=lambda c: c.shortName or c.longName):
|
|
pluginText = ""
|
|
if cmd.isPlugin:
|
|
pluginText = f"{vt100.CYAN}(plugin){vt100.RESET}"
|
|
|
|
print(
|
|
f" {vt100.GREEN}{cmd.shortName or ' '}{vt100.RESET} {cmd.longName} - {cmd.helpText} {pluginText}"
|
|
)
|
|
|
|
print()
|
|
vt100.title("Logging")
|
|
print(" Logs are stored in:")
|
|
print(f" - {const.PROJECT_LOG_FILE}")
|
|
print(f" - {const.GLOBAL_LOG_FILE}")
|
|
|
|
|
|
@command("v", "version", "Show current version")
|
|
def versionCmd(args: Args):
|
|
print(f"CuteKit v{const.VERSION_STR}")
|
|
|
|
|
|
def exec(args: Args):
|
|
cmd = args.consumeArg()
|
|
|
|
if cmd is None:
|
|
raise RuntimeError("No command specified")
|
|
|
|
for c in commands:
|
|
if c.shortName == cmd or c.longName == cmd:
|
|
c.callback(args)
|
|
return
|
|
|
|
raise RuntimeError(f"Unknown command {cmd}")
|