fix(map): fail on blank rows inside a layer, keep trailing-space cells verbatim
This commit is contained in:
@@ -89,14 +89,27 @@ class _MapFile(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
def _split_rows(block: str, what: str, path: Path) -> tuple[str, ...]:
|
def _split_rows(block: str, what: str, path: Path) -> tuple[str, ...]:
|
||||||
rows = tuple(line.rstrip() for line in block.splitlines() if line.strip())
|
"""Split an ASCII layer into equal-width rows, failing loudly on gaps.
|
||||||
if not rows:
|
|
||||||
|
Trailing empty lines are a YAML block-scalar artifact and dropped; an
|
||||||
|
empty line *inside* the layer is a missing row -- a data error, never a
|
||||||
|
silently dropped one. Rows are kept verbatim (no rstrip): trailing spaces
|
||||||
|
are cells whenever " " is in the legend, and the width/char checks catch
|
||||||
|
them otherwise.
|
||||||
|
"""
|
||||||
|
lines = block.splitlines()
|
||||||
|
while lines and lines[-1] == "":
|
||||||
|
lines.pop()
|
||||||
|
if not lines:
|
||||||
msg = f"{what} layer is empty"
|
msg = f"{what} layer is empty"
|
||||||
raise MapValidationError(path, msg)
|
raise MapValidationError(path, msg)
|
||||||
if len({len(row) for row in rows}) != 1:
|
if any(line == "" for line in lines):
|
||||||
|
msg = f"{what} layer has a blank row inside the layer"
|
||||||
|
raise MapValidationError(path, msg)
|
||||||
|
if len({len(row) for row in lines}) != 1:
|
||||||
msg = f"{what} layer has ragged rows"
|
msg = f"{what} layer has ragged rows"
|
||||||
raise MapValidationError(path, msg)
|
raise MapValidationError(path, msg)
|
||||||
return rows
|
return tuple(lines)
|
||||||
|
|
||||||
|
|
||||||
def _check_layer_dimensions(
|
def _check_layer_dimensions(
|
||||||
|
|||||||
@@ -73,6 +73,35 @@ def test_rejects_ragged_rows(tmp_path: Path) -> None:
|
|||||||
load_map(_write(tmp_path, body))
|
load_map(_write(tmp_path, body))
|
||||||
|
|
||||||
|
|
||||||
|
def test_rejects_blank_row_inside_layer(tmp_path: Path) -> None:
|
||||||
|
"""A truly blank line inside a layer is a missing row -- an error, never
|
||||||
|
silently dropped (the old filter shrank the row count unnoticed)."""
|
||||||
|
body = _minimal_map().replace(" ###\n #.#\n", " ###\n\n #.#\n")
|
||||||
|
with pytest.raises(MapValidationError, match="blank row"):
|
||||||
|
load_map(_write(tmp_path, body))
|
||||||
|
|
||||||
|
|
||||||
|
def test_trailing_space_cells_are_kept(tmp_path: Path) -> None:
|
||||||
|
"""With " " in the legend, trailing spaces are real cells: rstrip() must
|
||||||
|
not delete them (the layer keeps its full width)."""
|
||||||
|
body = (
|
||||||
|
'\nname: "Test"\nterrain: |\n ...\n .. \n'
|
||||||
|
'legend:\n'
|
||||||
|
' ".": { type: floor, move_cost: 1 }\n'
|
||||||
|
' " ": { type: floor, move_cost: 1 }\n'
|
||||||
|
)
|
||||||
|
spec = load_map(_write(tmp_path, body))
|
||||||
|
assert spec.terrain == ("...", ".. ")
|
||||||
|
|
||||||
|
|
||||||
|
def test_rejects_trailing_space_without_legend_entry(tmp_path: Path) -> None:
|
||||||
|
"""Without " " in the legend a padded row is ragged/unknown-char data,
|
||||||
|
reported as an error instead of silently stripped."""
|
||||||
|
body = _minimal_map().replace(" ###\n #.#\n ###\n", " ### \n #.# \n ### \n")
|
||||||
|
with pytest.raises(MapValidationError):
|
||||||
|
load_map(_write(tmp_path, body))
|
||||||
|
|
||||||
|
|
||||||
def test_rejects_non_five_foot_square_size(tmp_path: Path) -> None:
|
def test_rejects_non_five_foot_square_size(tmp_path: Path) -> None:
|
||||||
"""SRD Measuring Distance: one grid square is 5 ft; the engine geometry
|
"""SRD Measuring Distance: one grid square is 5 ft; the engine geometry
|
||||||
is hard-coded to that scale, so any other declared size is a load error
|
is hard-coded to that scale, so any other declared size is a load error
|
||||||
|
|||||||
Reference in New Issue
Block a user