Initial setup of dmsh
This commit is contained in:
@@ -0,0 +1 @@
|
||||
from .base import BasePrompt
|
||||
@@ -0,0 +1,125 @@
|
||||
import functools
|
||||
from collections import namedtuple, defaultdict
|
||||
|
||||
from prompt_toolkit.completion import NestedCompleter
|
||||
|
||||
|
||||
from site_tools.console import Console
|
||||
from textwrap import dedent
|
||||
|
||||
COMMANDS = defaultdict(dict)
|
||||
|
||||
Command = namedtuple('Commmand', 'prompt,handler,usage,completer')
|
||||
|
||||
|
||||
def register_command(handler, usage, completer=None):
|
||||
prompt = handler.__qualname__.split('.', -1)[0]
|
||||
cmd = handler.__name__
|
||||
if cmd not in COMMANDS[prompt]:
|
||||
COMMANDS[prompt][cmd] = Command(
|
||||
prompt=prompt,
|
||||
handler=handler,
|
||||
usage=usage,
|
||||
completer=completer,
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
class BasePrompt(NestedCompleter):
|
||||
|
||||
def __init__(self, console=None):
|
||||
super(BasePrompt, self).__init__(self._nested_completer_map())
|
||||
|
||||
self._prompt = ''
|
||||
self._autocomplete_values = []
|
||||
self._console = None
|
||||
self._theme = None
|
||||
|
||||
def _nested_completer_map(self):
|
||||
return dict(
|
||||
(cmd_name, cmd.completer) for (cmd_name, cmd) in COMMANDS[self.__class__.__name__].items()
|
||||
)
|
||||
|
||||
def _get_help(self, cmd=None):
|
||||
try:
|
||||
return dedent(COMMANDS[self.__class__.__name__][cmd].usage)
|
||||
except KeyError:
|
||||
return self.usage
|
||||
|
||||
def default_completer(self, document, complete_event):
|
||||
raise NotImplementedError(f"Implement the 'default_completer' method of {self.__class__.__name__}")
|
||||
|
||||
@property
|
||||
def usage(self):
|
||||
text = dedent("""
|
||||
[title]dmsh[/title]
|
||||
|
||||
Available commands are listed below. Try 'help COMMAND' for detailed help.
|
||||
|
||||
[title]COMMANDS[/title]
|
||||
|
||||
""")
|
||||
for (name, cmd) in sorted(self.commands.items()):
|
||||
text += f" [b]{name:10s}[/b] {cmd.handler.__doc__.strip()}\n"
|
||||
return text
|
||||
|
||||
@property
|
||||
def commands(self):
|
||||
return COMMANDS[self.__class__.__name__]
|
||||
|
||||
@property
|
||||
def console(self):
|
||||
if not self._console:
|
||||
self._console = Console(color_system='truecolor')
|
||||
return self._console
|
||||
|
||||
@property
|
||||
def prompt(self):
|
||||
return self._prompt
|
||||
|
||||
@property
|
||||
def autocomplete_values(self):
|
||||
return self._autocomplete_values
|
||||
|
||||
@property
|
||||
def toolbar(self):
|
||||
return None
|
||||
|
||||
@property
|
||||
def key_bindings(self):
|
||||
return None
|
||||
|
||||
def help(self, parts):
|
||||
attr = None
|
||||
if parts:
|
||||
attr = parts[0]
|
||||
self.console.print(self._get_help(attr))
|
||||
return True
|
||||
|
||||
def process(self, cmd, *parts):
|
||||
if cmd in self.commands:
|
||||
return self.commands[cmd].handler(self, parts)
|
||||
self.console.error(f"Command {cmd} not understood.")
|
||||
|
||||
def start(self, cmd=None):
|
||||
while True:
|
||||
if not cmd:
|
||||
cmd = self.console.prompt(
|
||||
self.prompt,
|
||||
completer=self,
|
||||
bottom_toolbar=self.toolbar,
|
||||
key_bindings=self.key_bindings)
|
||||
if cmd:
|
||||
cmd, *parts = cmd.split()
|
||||
self.process(cmd, *parts)
|
||||
cmd = None
|
||||
@@ -0,0 +1,191 @@
|
||||
from site_tools.shell.base import BasePrompt, command
|
||||
from rolltable.tables import RollTable
|
||||
from rich.table import Table
|
||||
from pathlib import Path
|
||||
from prompt_toolkit.completion import WordCompleter
|
||||
from prompt_toolkit.key_binding import KeyBindings
|
||||
from prompt_toolkit.application import get_app
|
||||
|
||||
|
||||
bindings = KeyBindings()
|
||||
|
||||
|
||||
class InteractiveShell(BasePrompt):
|
||||
|
||||
def __init__(self, prompt=[], config={}, session={}):
|
||||
super().__init__()
|
||||
self._prompt = prompt
|
||||
self._config = config
|
||||
self._wmt = ""
|
||||
self._subshells = {}
|
||||
self._register_subshells()
|
||||
self._register_keybindings()
|
||||
self._session = session
|
||||
|
||||
def _register_keybindings(self):
|
||||
|
||||
@bindings.add('c-q')
|
||||
@bindings.add('c-d')
|
||||
def quit(event):
|
||||
self.quit()
|
||||
|
||||
@bindings.add('c-h')
|
||||
def help(event):
|
||||
self.help()
|
||||
|
||||
@bindings.add('c-w')
|
||||
def wmt(event):
|
||||
self.wmt()
|
||||
|
||||
def _register_subshells(self):
|
||||
for subclass in BasePrompt.__subclasses__():
|
||||
if subclass.__name__ == self.__class__.__name__:
|
||||
continue
|
||||
self._subshells[subclass.__name__] = subclass(parent=self)
|
||||
|
||||
@property
|
||||
def key_bindings(self):
|
||||
return bindings
|
||||
|
||||
@property
|
||||
def toolbar(self):
|
||||
return [
|
||||
('class:bold', ' DMSH '),
|
||||
('', " [H]elp "),
|
||||
('', " [W]mt "),
|
||||
('', " [Q]uit "),
|
||||
]
|
||||
|
||||
@property
|
||||
def session(self):
|
||||
return self._session
|
||||
|
||||
@property
|
||||
def autocomplete_values(self):
|
||||
return list(self.commands.keys())
|
||||
|
||||
def default_completer(self, document, complete_event): # pragma: no cover
|
||||
word = document.current_line_before_cursor
|
||||
raise Exception(word)
|
||||
|
||||
def process(self, cmd, *parts):
|
||||
if cmd in self.commands:
|
||||
return self.commands[cmd].handler(self, parts)
|
||||
return "Unknown Command; try help."
|
||||
|
||||
@command(usage="""
|
||||
[title]QUIT[/title]
|
||||
|
||||
The [b]quit[/b] command exits dmsh.
|
||||
|
||||
[title]USAGE[/title]
|
||||
|
||||
[link]> quit|^D|<ENTER>[/link]
|
||||
""")
|
||||
def quit(self, *parts):
|
||||
"""
|
||||
Quit dmsh.
|
||||
"""
|
||||
get_app().exit()
|
||||
raise SystemExit("Okay BYEEEE")
|
||||
|
||||
@command(usage="""
|
||||
[title]HELP FOR THE HELP LORD[/title]
|
||||
|
||||
The [b]help[/b] command will print usage information for whatever you're currently
|
||||
doing. You can also ask for help on any command currently available.
|
||||
|
||||
[title]USAGE[/title]
|
||||
|
||||
[link]> help [COMMAND][/link]
|
||||
""")
|
||||
def help(self, *parts):
|
||||
"""
|
||||
Display the help message.
|
||||
"""
|
||||
super().help(parts)
|
||||
return True
|
||||
|
||||
@command(usage="""
|
||||
[title]INCREMENT DATE[/title]
|
||||
|
||||
[b]id[/b] Increments the calendar date by one day.
|
||||
|
||||
[title]USAGE[/title]
|
||||
|
||||
[link]id[/link]
|
||||
""")
|
||||
def id(self, *parts):
|
||||
"""
|
||||
Increment the date by one day.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@command(usage="""
|
||||
[title]LOCATION[/title]
|
||||
|
||||
[b]loc[/b] sets the party's location to the specified region of the Sahwat Desert.
|
||||
|
||||
[title]USAGE[/title]
|
||||
|
||||
[link]loc LOCATION[/link]
|
||||
""",
|
||||
completer=WordCompleter([
|
||||
"The Blooming Wastes",
|
||||
"Dust River Canyon",
|
||||
"Gopher Gulch",
|
||||
"Calamity Ridge"
|
||||
]))
|
||||
def loc(self, *parts):
|
||||
"""
|
||||
Move the party to a new region of the Sahwat Desert.
|
||||
"""
|
||||
if parts:
|
||||
self.session['location'] = (' '.join(parts))
|
||||
self.console.print(f"The party is in {self.session['location']}.")
|
||||
|
||||
@command(usage="""
|
||||
[title]OVERLAND TRAVEL[/title]
|
||||
|
||||
[b]ot[/b]
|
||||
|
||||
[title]USAGE[/title]
|
||||
|
||||
[link]ot in[/link]
|
||||
""")
|
||||
def ot(self, *parts):
|
||||
"""
|
||||
Increment the date by one day and record
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@command(usage="""
|
||||
[title]WILD MAGIC TABLE[/title]
|
||||
|
||||
[b]wmt[/b] Generates a d20 wild magic surge roll table. The table will be cached for the session.
|
||||
|
||||
[title]USAGE[/title]
|
||||
|
||||
[link]> wmt[/link]
|
||||
|
||||
[title]CLI[/title]
|
||||
|
||||
[link]roll-table \\
|
||||
sources/sahwat_magic_table.yaml \\
|
||||
--frequency default --die 20[/link]
|
||||
""")
|
||||
def wmt(self, *parts, source='sahwat_magic_table.yaml'):
|
||||
"""
|
||||
Generate a Wild Magic Table for resolving spell effects.
|
||||
"""
|
||||
if not self._wmt:
|
||||
rt = RollTable(
|
||||
[Path(f"{self._config['table_sources_path']}/{source}").read_text()],
|
||||
frequency='default',
|
||||
die=20,
|
||||
)
|
||||
table = Table(*rt.expanded_rows[0])
|
||||
for row in rt.expanded_rows[1:]:
|
||||
table.add_row(*row)
|
||||
self._wmt = table
|
||||
self.console.print(self._wmt)
|
||||
Reference in New Issue
Block a user