cli/validate: wrap errors in types, improve formatting
This commit is contained in:
@@ -1,16 +1,22 @@
|
||||
from dataclasses import dataclass
|
||||
from textwrap import indent
|
||||
from typing import Any, Callable
|
||||
|
||||
from lib_marker import MarkerSet, Point, Track
|
||||
from marker_sets import WORLDS
|
||||
from lib_marker import Point, Track
|
||||
|
||||
|
||||
def main() -> None:
|
||||
result_is_ok = True
|
||||
for test_name, test_f in _tests.items():
|
||||
results = test_f()
|
||||
results = test_f(WORLDS)
|
||||
if len(results) > 0:
|
||||
result_is_ok = False
|
||||
print(f"[X] {test_name}")
|
||||
print()
|
||||
for result in results:
|
||||
print(f" {result}")
|
||||
print(indent(result.pretty_print(), " "))
|
||||
print()
|
||||
else:
|
||||
print(f"[✓] {test_name}")
|
||||
|
||||
@@ -18,126 +24,200 @@ def main() -> None:
|
||||
exit(1)
|
||||
|
||||
|
||||
def validate_no_non_included_files() -> list:
|
||||
class ValidationError:
|
||||
error_type: str
|
||||
world_name: str | None
|
||||
marker_set_name: str | None
|
||||
marker_name: str | None
|
||||
note: str | None
|
||||
details: dict[str, Any]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
error_type: str,
|
||||
world_name: str | None = None,
|
||||
marker_set_name: str | None = None,
|
||||
marker_name: str | None = None,
|
||||
note: str | None = None,
|
||||
**details,
|
||||
):
|
||||
self.error_type = error_type
|
||||
self.world_name = world_name
|
||||
self.marker_set_name = marker_set_name
|
||||
self.marker_name = marker_name
|
||||
self.note = note
|
||||
self.details = details
|
||||
|
||||
def pretty_print(self) -> str:
|
||||
result = []
|
||||
|
||||
location_parts = [
|
||||
x
|
||||
for x in (self.world_name, self.marker_set_name, self.marker_name)
|
||||
if x is not None
|
||||
]
|
||||
if len(location_parts) > 0:
|
||||
result.append(f"[{'/'.join(location_parts)}] ")
|
||||
else:
|
||||
result.append("[]")
|
||||
|
||||
result.append(indent(f"type: {self.error_type}", " "))
|
||||
|
||||
if self.note:
|
||||
result.append(indent(f"note: {self.note.removesuffix('.')}", " "))
|
||||
|
||||
for key, value in self.details.items():
|
||||
result.append(indent(f"{key}: {value}", " "))
|
||||
|
||||
return "\n".join(result)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ValidationErrorPoint:
|
||||
x: int
|
||||
y: int
|
||||
z: int
|
||||
|
||||
def __init__(self, x: int, y: int, z: int) -> None:
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.z = z
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"({self.x}, {self.y}, {self.z})"
|
||||
|
||||
|
||||
def validate_no_non_included_files(
|
||||
worlds: dict[str, list[MarkerSet]],
|
||||
) -> list[ValidationError]:
|
||||
# TODO: Implement this function
|
||||
return []
|
||||
|
||||
|
||||
def validate_no_invalid_y_values() -> list:
|
||||
def validate_no_invalid_y_values(
|
||||
worlds: dict[str, list[MarkerSet]],
|
||||
) -> list[ValidationError]:
|
||||
result = []
|
||||
|
||||
for world_name, marker_sets in WORLDS.items():
|
||||
for world_name, marker_sets in worlds.items():
|
||||
for marker_set in marker_sets:
|
||||
for marker in marker_set.markers:
|
||||
if isinstance(marker, Point):
|
||||
if marker.y < -64 or marker.y > 255:
|
||||
result.append(
|
||||
{
|
||||
"error_type": "invalid_y_value",
|
||||
"full_marker_name": (
|
||||
world_name,
|
||||
marker_set.name,
|
||||
marker.name,
|
||||
),
|
||||
"y_value": marker.y,
|
||||
}
|
||||
ValidationError(
|
||||
error_type="invalid_y_value",
|
||||
world_name=world_name,
|
||||
marker_set_name=marker_set.name,
|
||||
marker_name=marker.name,
|
||||
y_value=marker.y,
|
||||
note="Point has coordinate above 255 or below -64.",
|
||||
)
|
||||
)
|
||||
elif isinstance(marker, Track):
|
||||
for i, point in enumerate(marker.points):
|
||||
_, y, _ = point
|
||||
if y < -64 or y > 255:
|
||||
result.append(
|
||||
{
|
||||
"error_type": "invalid_y_value",
|
||||
"full_marker_name": (
|
||||
world_name,
|
||||
marker_set.name,
|
||||
marker.name,
|
||||
),
|
||||
"index": i,
|
||||
"y_value": y,
|
||||
}
|
||||
ValidationError(
|
||||
error_type="invalid_y_value",
|
||||
world_name=world_name,
|
||||
marker_set_name=marker_set.name,
|
||||
marker_name=marker.name,
|
||||
index=i,
|
||||
y_value=y,
|
||||
note="Track point has coordinate above 255 or below -64.",
|
||||
)
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def validate_no_duplicate_points_in_tracks() -> list:
|
||||
def validate_no_consecutive_duplicate_points_in_tracks(
|
||||
worlds: dict[str, list[MarkerSet]],
|
||||
) -> list[ValidationError]:
|
||||
result = []
|
||||
|
||||
for world_name, marker_sets in WORLDS.items():
|
||||
for world_name, marker_sets in worlds.items():
|
||||
for marker_set in marker_sets:
|
||||
for marker in marker_set.markers:
|
||||
if isinstance(marker, Track):
|
||||
for p1, p2 in zip(marker.points, marker.points[1:]):
|
||||
if p1 == p2:
|
||||
result.append(
|
||||
{
|
||||
"error_type": "duplicate_point",
|
||||
"point_a": (
|
||||
world_name,
|
||||
marker_set.name,
|
||||
marker.name,
|
||||
),
|
||||
"point_b": (
|
||||
world_name,
|
||||
marker_set.name,
|
||||
marker.name,
|
||||
),
|
||||
"coordinates": p1,
|
||||
}
|
||||
ValidationError(
|
||||
error_type="duplicate_point_in_track",
|
||||
world_name=world_name,
|
||||
marker_set_name=marker_set.name,
|
||||
marker_name=marker.name,
|
||||
point=ValidationErrorPoint(*p1),
|
||||
note="Consecutive points in a track must not be identical, this is likely a copy-paste error.",
|
||||
)
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def validate_no_duplicate_marker_names() -> list:
|
||||
def validate_no_duplicate_marker_names(
|
||||
worlds: dict[str, list[MarkerSet]],
|
||||
) -> list[ValidationError]:
|
||||
result = []
|
||||
|
||||
marker_names = set()
|
||||
for world_name, marker_sets in WORLDS.items():
|
||||
for world_name, marker_sets in worlds.items():
|
||||
for marker_set in marker_sets:
|
||||
for marker in marker_set.markers:
|
||||
full_name = (world_name, marker_set.name, marker.name)
|
||||
if full_name in marker_names:
|
||||
result.append(
|
||||
{
|
||||
"error_type": "duplicate_marker_name",
|
||||
"full_marker_name": full_name,
|
||||
}
|
||||
ValidationError(
|
||||
error_type="duplicate_marker_name",
|
||||
world_name=world_name,
|
||||
marker_set_name=marker_set.name,
|
||||
marker_name=marker.name,
|
||||
note="Marker names must be unique within a marker set.",
|
||||
)
|
||||
)
|
||||
marker_names.add(full_name)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def validate_no_duplicate_marker_set_names() -> list:
|
||||
def validate_no_duplicate_marker_set_names(
|
||||
worlds: dict[str, list[MarkerSet]],
|
||||
) -> list[ValidationError]:
|
||||
result = []
|
||||
|
||||
marker_set_names = set()
|
||||
for world_name, marker_sets in WORLDS.items():
|
||||
for world_name, marker_sets in worlds.items():
|
||||
for marker_set in marker_sets:
|
||||
if (world_name, marker_set.name) in marker_set_names:
|
||||
result.append(
|
||||
{
|
||||
"error_type": "duplicate_marker_set_name",
|
||||
"full_marker_set_name": (world_name, marker_set.name),
|
||||
}
|
||||
ValidationError(
|
||||
error_type="duplicate_marker_set_name",
|
||||
world_name=world_name,
|
||||
marker_set_name=marker_set.name,
|
||||
note="Marker set names must be unique within a world.",
|
||||
)
|
||||
)
|
||||
marker_set_names.add((world_name, marker_set.name))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def validate_no_unused_icons() -> list:
|
||||
def validate_no_unused_icons(
|
||||
worlds: dict[str, list[MarkerSet]],
|
||||
) -> list[ValidationError]:
|
||||
# TODO: Implement this function
|
||||
return []
|
||||
|
||||
|
||||
def validate_no_duplicate_points() -> list:
|
||||
def validate_no_duplicate_points(
|
||||
worlds: dict[str, list[MarkerSet]],
|
||||
) -> list[ValidationError]:
|
||||
result = []
|
||||
|
||||
for world_name, marker_sets in WORLDS.items():
|
||||
for world_name, marker_sets in worlds.items():
|
||||
points = {}
|
||||
for marker_set in marker_sets:
|
||||
for marker in marker_set.markers:
|
||||
@@ -145,43 +225,48 @@ def validate_no_duplicate_points() -> list:
|
||||
point = (marker.x, marker.y, marker.z)
|
||||
if point in points:
|
||||
result.append(
|
||||
{
|
||||
"error_type": "duplicate_point",
|
||||
"point_a": points[point],
|
||||
"point_b": (world_name, marker_set.name, marker.name),
|
||||
"coordinates": point,
|
||||
}
|
||||
ValidationError(
|
||||
error_type="duplicate_point",
|
||||
world_name=world_name,
|
||||
marker_set_name=points[point][1],
|
||||
marker_name=points[point][2],
|
||||
other_marker_set_name=marker_set.name,
|
||||
other_marker_name=marker.name,
|
||||
location=ValidationErrorPoint(*point),
|
||||
note="Two markers should not share the same coordinates.",
|
||||
)
|
||||
)
|
||||
points[point] = (world_name, marker_set.name, marker.name)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def validate_all_tracks_have_at_least_two_points() -> list:
|
||||
def validate_all_tracks_have_at_least_two_points(
|
||||
worlds: dict[str, list[MarkerSet]],
|
||||
) -> list[ValidationError]:
|
||||
result = []
|
||||
|
||||
for world_name, marker_sets in WORLDS.items():
|
||||
for world_name, marker_sets in worlds.items():
|
||||
for marker_set in marker_sets:
|
||||
for marker in marker_set.markers:
|
||||
if isinstance(marker, Track) and len(marker.points) < 2:
|
||||
result.append(
|
||||
{
|
||||
"error_type": "track_too_short",
|
||||
"full_marker_name": (
|
||||
world_name,
|
||||
marker_set.name,
|
||||
marker.name,
|
||||
),
|
||||
}
|
||||
ValidationError(
|
||||
error_type="track_too_short",
|
||||
world_name=world_name,
|
||||
marker_set_name=marker_set.name,
|
||||
marker_name=marker.name,
|
||||
note="Tracks must have at least two points.",
|
||||
)
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
_tests = {
|
||||
_tests: dict[str, Callable[[dict[str, list[MarkerSet]]], list[ValidationError]]] = {
|
||||
"No non-included files": validate_no_non_included_files,
|
||||
"No invalid y coordinates": validate_no_invalid_y_values,
|
||||
"No duplicate points in tracks": validate_no_duplicate_points_in_tracks,
|
||||
"No consecutive duplicate points in tracks": validate_no_consecutive_duplicate_points_in_tracks,
|
||||
"No duplicate marker names": validate_no_duplicate_marker_names,
|
||||
"No duplicate marker-set names": validate_no_duplicate_marker_set_names,
|
||||
"No unused icons": validate_no_unused_icons,
|
||||
|
||||
Reference in New Issue
Block a user