Added CLI options for controlling text output
This commit is contained in:
parent
2cddc902ab
commit
ae3d40e91e
|
@ -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 = [""]
|
||||||
left_coords = self.left_coordinates
|
|
||||||
i = 0
|
map_lines = ""
|
||||||
for line in str(self).splitlines():
|
if self.render_coordinates:
|
||||||
lines += f"{left_coords[i].rjust(2, ' ')} │ {line}".ljust(self.width, " ") + " │\n"
|
left_coords = self.left_coordinates
|
||||||
i = i + 1
|
i = 0
|
||||||
top_break = "┌" + ("─" * (self.width + 2)) + "┐"
|
border = "│" if self.render_border else " "
|
||||||
bot_break = "└" + ("─" * (self.width + 2)) + "┘"
|
for line in str(self).splitlines():
|
||||||
return "\n".join(
|
map_lines += f"{left_coords[i].rjust(2, ' ')}{border}{line}".ljust(self.width, " ") + f"{border}\n"
|
||||||
[
|
i = i + 1
|
||||||
"",
|
elif self.render_border:
|
||||||
self.header,
|
for line in str(self).splitlines():
|
||||||
"",
|
map_lines += f"│{line}".ljust(self.width, " ") + "│\n"
|
||||||
indent(self.top_coordinates, " " * 5),
|
else:
|
||||||
indent(top_break, " " * 3),
|
map_lines = str(self)
|
||||||
lines.rstrip(),
|
|
||||||
indent(bot_break, " " * 3),
|
top_break = bot_break = ""
|
||||||
"",
|
if self.render_border:
|
||||||
indent(self.footer, " ")
|
top_break = "┌" + ("─" * self.width) + "┐"
|
||||||
]
|
bot_break = "└" + ("─" * self.width) + "┘"
|
||||||
)
|
|
||||||
|
lines = [self.header] if self.render_header else []
|
||||||
|
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), " ")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user