dnd/deadsands/site_tools/shell/base.py

134 lines
3.6 KiB
Python
Raw Normal View History

2023-07-03 14:14:03 -07:00
import functools
2023-07-04 10:59:51 -07:00
from collections import defaultdict, namedtuple
from textwrap import dedent
2023-07-03 14:14:03 -07:00
from prompt_toolkit.completion import NestedCompleter
from site_tools.console import Console
COMMANDS = defaultdict(dict)
2023-07-04 10:59:51 -07:00
Command = namedtuple("Commmand", "prompt,handler,usage,completer")
2023-07-03 14:14:03 -07:00
2023-07-04 11:17:28 -07:00
2023-07-03 14:14:03 -07:00
def register_command(handler, usage, completer=None):
2023-07-04 10:59:51 -07:00
prompt = handler.__qualname__.split(".", -1)[0]
2023-07-03 14:14:03 -07:00
cmd = handler.__name__
if cmd not in COMMANDS[prompt]:
COMMANDS[prompt][cmd] = Command(
prompt=prompt,
handler=handler,
usage=usage,
completer=completer,
)
2023-07-04 11:17:28 -07:00
2023-07-03 14:14:03 -07:00
def command(usage, completer=None, binding=None):
def decorator(func):
register_command(func, usage, completer)
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
return decorator
2023-07-04 11:17:28 -07:00
2023-07-03 14:14:03 -07:00
class BasePrompt(NestedCompleter):
2023-07-04 10:45:10 -07:00
def __init__(self, cache={}):
2023-07-03 14:14:03 -07:00
super(BasePrompt, self).__init__(self._nested_completer_map())
2023-07-04 10:59:51 -07:00
self._prompt = ""
2023-07-03 14:14:03 -07:00
self._console = None
self._theme = None
2023-07-04 10:45:10 -07:00
self._toolbar = None
self._key_bindings = None
self._subshells = {}
self._cache = cache
2023-07-04 10:59:51 -07:00
self._name = "Interactive Shell"
2023-07-04 11:17:28 -07:00
2023-07-04 10:45:10 -07:00
def _register_subshells(self):
for subclass in BasePrompt.__subclasses__():
if subclass.__name__ == self.__class__.__name__:
continue
self._subshells[subclass.__name__] = subclass(parent=self)
2023-07-04 11:17:28 -07:00
2023-07-03 14:14:03 -07:00
def _nested_completer_map(self):
2023-07-04 10:59:51 -07:00
return dict((cmd_name, cmd.completer) for (cmd_name, cmd) in COMMANDS[self.__class__.__name__].items())
2023-07-04 11:17:28 -07:00
2023-07-03 14:14:03 -07:00
def _get_help(self, cmd=None):
try:
return dedent(COMMANDS[self.__class__.__name__][cmd].usage)
except KeyError:
return self.usage
2023-07-04 11:17:28 -07:00
2023-07-04 10:45:10 -07:00
@property
def name(self):
return self._name
2023-07-04 11:17:28 -07:00
2023-07-04 10:45:10 -07:00
@property
def cache(self):
return self._cache
2023-07-04 11:17:28 -07:00
2023-07-04 10:45:10 -07:00
@property
def key_bindings(self):
return self._key_bindings
2023-07-04 11:17:28 -07:00
2023-07-03 14:14:03 -07:00
@property
def usage(self):
2023-07-04 10:45:10 -07:00
text = dedent(f"""
[title]{self.name}[/title]
2023-07-03 14:14:03 -07:00
Available commands are listed below. Try 'help COMMAND' for detailed help.
[title]COMMANDS[/title]
""")
2023-07-04 10:59:51 -07:00
for name, cmd in sorted(self.commands.items()):
2023-07-03 14:14:03 -07:00
text += f" [b]{name:10s}[/b] {cmd.handler.__doc__.strip()}\n"
return text
2023-07-04 11:17:28 -07:00
2023-07-03 14:14:03 -07:00
@property
def commands(self):
return COMMANDS[self.__class__.__name__]
2023-07-04 11:17:28 -07:00
2023-07-03 14:14:03 -07:00
@property
def console(self):
if not self._console:
2023-07-04 10:59:51 -07:00
self._console = Console(color_system="truecolor")
2023-07-03 14:14:03 -07:00
return self._console
2023-07-04 11:17:28 -07:00
2023-07-03 14:14:03 -07:00
@property
def prompt(self):
return self._prompt
2023-07-04 11:17:28 -07:00
2023-07-03 14:14:03 -07:00
@property
def autocomplete_values(self):
2023-07-04 10:45:10 -07:00
return list(self.commands.keys())
2023-07-04 11:17:28 -07:00
2023-07-03 14:14:03 -07:00
@property
def toolbar(self):
2023-07-04 10:45:10 -07:00
return self._toolbar
2023-07-04 11:17:28 -07:00
2023-07-03 14:14:03 -07:00
def help(self, parts):
attr = None
if parts:
attr = parts[0]
self.console.print(self._get_help(attr))
return True
2023-07-04 11:17:28 -07:00
2023-07-03 14:14:03 -07:00
def process(self, cmd, *parts):
if cmd in self.commands:
return self.commands[cmd].handler(self, parts)
2023-07-04 10:45:10 -07:00
self.console.error(f"Command {cmd} not understood; try 'help' for help.")
2023-07-04 11:17:28 -07:00
2023-07-03 14:14:03 -07:00
def start(self, cmd=None):
while True:
if not cmd:
cmd = self.console.prompt(
2023-07-04 10:59:51 -07:00
self.prompt, completer=self, bottom_toolbar=self.toolbar, key_bindings=self.key_bindings
)
2023-07-03 14:14:03 -07:00
if cmd:
cmd, *parts = cmd.split()
self.process(cmd, *parts)
cmd = None