Added CLI options for controlling text output

This commit is contained in:
evilchili 2025-08-17 19:29:09 -07:00
parent 2cddc902ab
commit ae3d40e91e
3 changed files with 108 additions and 34 deletions

View File

@ -1,13 +1,12 @@
# import logging # import logging
import tomllib
from dataclasses import dataclass from dataclasses import dataclass
from functools import cached_property from functools import cached_property
from io import StringIO from io import StringIO
from pathlib import Path from pathlib import Path
from textwrap import indent from textwrap import indent
from typing import List, Union, get_type_hints
from types import SimpleNamespace from types import SimpleNamespace
from typing import List, Union, get_type_hints
import tomllib
from PIL import Image from PIL import Image
@ -40,6 +39,13 @@ class BattleMap(BattleMapType):
map_string: str = "" map_string: str = ""
metadata: SimpleNamespace = None metadata: SimpleNamespace = None
tileset: TileSet = None tileset: TileSet = None
render_header: bool = True
render_border: bool = True
render_coordinates: bool = True
render_legend: bool = True
render_notes: bool = True
width: int = 0 width: int = 0
height: int = 0 height: int = 0
@ -62,7 +68,7 @@ class BattleMap(BattleMapType):
line = line.lstrip(" ") line = line.lstrip(" ")
if in_map: if in_map:
if line and line[0] == "#": if line and line[0] == "#":
map_data += line[1:] + "\n" map_data += line[1:].lstrip() + "\n"
continue continue
in_map = False in_map = False
metadata += line + "\n" metadata += line + "\n"
@ -82,6 +88,8 @@ class BattleMap(BattleMapType):
del self.legend del self.legend
if hasattr(self, "coordinates"): if hasattr(self, "coordinates"):
del self.coordinates del self.coordinates
if hasattr(self, "notes"):
del self.notes
return self.grid return self.grid
def validate_map_string(self, data: str) -> bool: def validate_map_string(self, data: str) -> bool:
@ -127,31 +135,46 @@ class BattleMap(BattleMapType):
@cached_property @cached_property
def header(self) -> str: def header(self) -> str:
lines = [f"BattleMap: {self.metadata.map['name']} ({self.width}x{self.height})"] lines = ["", f"BattleMap: {self.metadata.map['name']} ({self.width}x{self.height})"]
lines.append(('-' * len(lines[0]))) lines.append(("-" * len(lines[0])))
for key, value in self.metadata.map.items(): for key, value in self.metadata.map.items():
if key in ("name", "description"): if key in ("name", "description"):
continue continue
lines.append(f"{key.title()}: {value}") lines.append(f"{key.title()}: {value}")
lines.append("")
return "\n".join(lines) return "\n".join(lines)
@cached_property @cached_property
def footer(self) -> str: def footer(self) -> str:
lines = [] lines = []
if 'description' in self.metadata.map: if "description" in self.metadata.map:
lines.append(f"{self.metadata.map['description']}") lines.append(f"{self.metadata.map['description']}")
lines.append("") lines.append("")
lines.append("Legend:") lines.append("Legend:")
lines.append(self.legend) lines.append(self.legend)
return "\n".join(lines) return "\n".join(lines)
@cached_property
def notes(self) -> str:
lines = []
for number in dir(self.metadata):
if not number.isdigit():
continue
location = getattr(self.metadata, number)
lines.append(f"[{number}]{location.get('name', '')}")
if hasattr(location, "notes"):
for note in location.notes:
lines.append(f" {note}")
lines.append("")
return "\n".join(lines)
@cached_property @cached_property
def legend(self) -> str: def legend(self) -> str:
output = "" output = ""
locations = False locations = False
for char in sorted(set(list(self.map_string)), key=str.lower): for char in sorted(set(list(self.map_string)), key=str.lower):
if char in self.tileset.config.legend: if char in self.tileset.config.legend:
if char in "0123456789": if char.isdigit():
locations = True locations = True
continue continue
output += f"{char} - {self.tileset.config.legend[char]}\n" output += f"{char} - {self.tileset.config.legend[char]}\n"
@ -182,24 +205,42 @@ class BattleMap(BattleMapType):
return self.as_string() return self.as_string()
def __repr__(self) -> str: def __repr__(self) -> str:
lines = "" lines = [""]
map_lines = ""
if self.render_coordinates:
left_coords = self.left_coordinates left_coords = self.left_coordinates
i = 0 i = 0
border = "" if self.render_border else " "
for line in str(self).splitlines(): for line in str(self).splitlines():
lines += f"{left_coords[i].rjust(2, ' ')}{line}".ljust(self.width, " ") + "\n" map_lines += f"{left_coords[i].rjust(2, ' ')}{border}{line}".ljust(self.width, " ") + f"{border}\n"
i = i + 1 i = i + 1
top_break = "" + ("" * (self.width + 2)) + "" elif self.render_border:
bot_break = "" + ("" * (self.width + 2)) + "" for line in str(self).splitlines():
return "\n".join( map_lines += f"{line}".ljust(self.width, " ") + "\n"
[ else:
"", map_lines = str(self)
self.header,
"", top_break = bot_break = ""
indent(self.top_coordinates, " " * 5), if self.render_border:
indent(top_break, " " * 3), top_break = "" + ("" * self.width) + ""
lines.rstrip(), bot_break = "" + ("" * self.width) + ""
indent(bot_break, " " * 3),
"", lines = [self.header] if self.render_header else []
indent(self.footer, " ") if self.render_coordinates:
] lines.append(indent(self.top_coordinates, " " * 3))
) if top_break:
lines.append(indent(top_break, " " * 2) if self.render_coordinates else top_break)
lines.append(map_lines.rstrip())
if bot_break:
lines.append(indent(bot_break, " " * 2) if self.render_coordinates else bot_break)
lines.append("")
if self.render_legend:
lines.append(indent(self.footer, " "))
lines.append("")
if self.render_notes:
lines.append(self.notes)
return indent("\n".join(lines), " ")

View File

@ -54,31 +54,60 @@ def convert(source: typer.FileText = typer.Argument(help="The donjon.sh .json fi
@app.command() @app.command()
def render( def render(
source: typer.FileText = typer.Argument( source: typer.FileText = typer.Argument(
help="The battle map text file to load.", default=INSTALL_DIR / "examples" / "five_room_dungeon.txt" help="The battle map text file to load.", default=INSTALL_DIR / "examples" / "five_room_dungeon.toml"
), ),
outfile: Path = typer.Option(help="The file to create. If not specified, print to STDOUT", default=None), outfile: Path = typer.Option(help="The file to create. If not specified, print to STDOUT", default=None),
tileset: str = typer.Option( tileset: str = typer.Option(
help="The name of the tile set to use (run mapper list to see what's available).", default="colorized" help="The name of the tile set to use (run mapper list to see what's available).", default="colorized"
), ),
header: bool = typer.Option(help="If True, include the map header.", default=True),
border: bool = typer.Option(help="If True, draw the map border.", default=True),
coordinates: bool = typer.Option(help="If True, draw the coordinate outside the border.", default=True),
legend: bool = typer.Option(help="If True, include the legend.", default=True),
notes: bool = typer.Option(help="If True, include the notes.", default=True),
map_only: bool = typer.Option(
help="Equivalent to --no-header --no-border --no-coordinates --no-legend --no-notes", default=False
),
): ):
""" """
Create a rendered battle map using a tile set. Will generate a PNG file if the tile set supports it, Create a rendered battle map using a tile set. Will generate a PNG file if the tile set supports it,
otherwise text output. otherwise text output.
""" """
render_map(source, outfile, tileset) render_map(
source=source,
outfile=outfile,
tileset=tileset,
header=header and not map_only,
border=border and not map_only,
coordinates=coordinates and not map_only,
legend=legend and not map_only,
notes=notes and not map_only,
)
def render_map( def render_map(
source: typer.FileText = INSTALL_DIR / "examples" / "five_room_dungeon.txt", source: typer.FileText = INSTALL_DIR / "examples" / "five_room_dungeon.txt",
outfile: Union[Path, None] = None, outfile: Union[Path, None] = None,
tileset: str = "colorized", tileset: str = "colorized",
header: bool = True,
border: bool = True,
coordinates: bool = True,
legend: bool = True,
notes: bool = True,
): ):
manager = app_state["tileset_manager"] manager = app_state["tileset_manager"]
if tileset not in manager.available: if tileset not in manager.available:
raise RuntimeError(f"Could not locate the tile set {tileset} in {manager.config_dir}.") raise RuntimeError(f"Could not locate the tile set {tileset} in {manager.config_dir}.")
render_options = {
"render_header": header,
"render_border": border,
"render_coordinates": coordinates,
"render_notes": notes,
"render_legend": legend,
}
try: try:
bmap = battlemap.BattleMap(source=source, tileset=manager.load(tileset)) bmap = battlemap.BattleMap(source=source, tileset=manager.load(tileset), **render_options)
bmap.load() bmap.load()
except battlemap.UnsupportedTileException as e: except battlemap.UnsupportedTileException as e:
logging.error(e) logging.error(e)

View File

@ -35,6 +35,10 @@ def sample_map():
[1] [1]
name = "room 1" name = "room 1"
description = "A large empty room."
notes = [
"Locked door DC 15"
]
[2] [2]
name = "room 2" name = "room 2"
[3] [3]
@ -62,13 +66,13 @@ def test_renderer(manager, sample_map):
srclines = sample_map.splitlines()[1:] srclines = sample_map.splitlines()[1:]
strlines = str(test_map).splitlines() strlines = str(test_map).splitlines()
for i in range(len(strlines)): for i in range(len(strlines)):
assert strlines[i] == srclines[i].lstrip(' #').ljust(21, " ") assert strlines[i] == srclines[i].lstrip(" #").ljust(21, " ")
def test_grid_coordiates(manager): def test_grid_coordiates(manager):
coord_length = len(X_COORDS) coord_length = len(X_COORDS)
map_size = 2 * coord_length + 1 map_size = 2 * coord_length + 1
bigmap = StringIO((' # ' + ("." * map_size) + "\n") * map_size) bigmap = StringIO((" # " + ("." * map_size) + "\n") * map_size)
test_map = battlemap.BattleMap(source=bigmap, tileset=manager.load("ascii")) test_map = battlemap.BattleMap(source=bigmap, tileset=manager.load("ascii"))
test_map.load() test_map.load()
assert test_map.grid.data[-1][-1].coordinates == (f"{map_size - 1}", X_COORDS[0] * 3) assert test_map.grid.data[-1][-1].coordinates == (f"{map_size - 1}", X_COORDS[0] * 3)