Source code for opend6_tools.magic.spellbook
"""
This creates a Python Module with spell definitions.
The module will be a CLI application with a number of subcommands.
- ``python spell_module.py display`` will display the spells in RST format.
This is used by the publication process.
- ``python spell_module.py debug 'name'`` will write debugging output for a spell.
.. autofunction:: build_app
.. autofunction:: make_spell_doctest
"""
import doctest
from pathlib import Path
import sys
from typing import Annotated, Literal, Any # noqa: F401
from .output import *
from .workbook import *
import typer
[docs]
def make_spell_doctest(spell_book: list[Spell], book_attr_name: str = "spells") -> None:
"""
Given a book of spells, write a ``__test__`` definition, suitable for doctest.
If the spells have an "other_aspect" that includes a "Difficulty" aspect,
this can be extracted to make a target difficulty.
:param spell_book: The book with a list of spells.
:param book_attr_name: The attribute name for the book.
"""
print("__test__ = {")
for slot, spell in enumerate(spell_book):
try:
expected = int(spell.other_aspects["Difficulty"].format) # type: ignore
except KeyError:
# Ugh. Difficulty not included.
expected = 0
print(
f' "{spell.name}": ">>> {book_attr_name}[{slot}].difficulty\\n{expected}",'
)
print("}")
[docs]
def build_app(
book: list[Spell],
book_attr_name: str = "spells",
*,
rich_markup_mode: Literal["rich", "markdown"] | None = "rich",
) -> typer.Typer:
spellbook_app = typer.Typer(
help="Work with this collection of Spells.", rich_markup_mode=rich_markup_mode
)
@spellbook_app.command(name="display")
def display_command(
names: Annotated[
list[str] | None, typer.Argument(help="Optional sell names to extract")
] = None,
underline: Annotated[
str | None, typer.Option(help="RST underline to use, default is '~'")
] = None,
):
"""Write RST-formatted details of all definitions to STDOUT."""
options = {"spell_heading": underline} if underline is not None else {}
if names:
for spell in book:
if spell.name in names:
detail(spell, **options)
else:
detail(book, **options)
@spellbook_app.command(name="debug")
def debug_command(
names: Annotated[
list[str] | None, typer.Argument(help="Spell name (or number)")
] = None,
details: Annotated[bool, typer.Option(help="Show details")] = False,
):
"""Print debugging information for a specific definition to STDOUT"""
debug(book, names, details)
@spellbook_app.command(name="test")
def run_test_command(make: bool = False, verbose: bool = False): # pragma: no cover
"""Run the doctest examples, using the __test__ global.
If the --make option is present, it writes a suggested __test__ definition.
"""
module = sys.modules["__main__"]
print(f"Testing {Path(cast(str, module.__file__)).relative_to(Path.cwd())}")
if make:
make_spell_doctest(book, book_attr_name)
sys.exit()
if not hasattr(module, "__test__"):
print(
"No __test__ found. If present, it must be **before** the build_app(). The --make option can be used to create a template."
)
sys.exit(2)
else:
failures, tests = doctest.testmod(module, verbose=verbose)
sys.exit(failures)
return spellbook_app