feat(tt_tools): --check-docs now prints all errors

Previously, it only printed the first error it encountered, causing more iterations when fixing info.yaml and info.md errors
This commit is contained in:
Uri Shaked
2025-10-03 18:25:42 +03:00
parent bdff4ca192
commit 03ca42307f
5 changed files with 272 additions and 90 deletions
+100 -67
View File
@@ -4,26 +4,35 @@ YAML_VERSION = 6
class ProjectYamlError(Exception):
pass
def __init__(self, errors: List[str]):
if isinstance(errors, str):
errors = [errors]
self.errors = errors
super().__init__(", ".join(errors))
class PinoutSection:
def __init__(self, yaml_data: Dict[str, Any]):
def __init__(self, yaml_data: Dict[str, Any], errors: List[str]):
self.__nonEmptyPins = 0
yaml_data = yaml_data.copy()
self.ui = self._pins(yaml_data, "ui", 8)
self.uo = self._pins(yaml_data, "uo", 8)
self.uio = self._pins(yaml_data, "uio", 8)
self.ua = self._pins(yaml_data, "ua", 6, True)
self.ui = self._pins(yaml_data, "ui", 8, errors)
self.uo = self._pins(yaml_data, "uo", 8, errors)
self.uio = self._pins(yaml_data, "uio", 8, errors)
self.ua = self._pins(yaml_data, "ua", 6, errors, True)
if self.__nonEmptyPins == 0:
raise ProjectYamlError("Please fill in the 'pinout' section")
errors.append("Please fill in the 'pinout' section")
if len(yaml_data) > 0:
raise ProjectYamlError(
errors.append(
f"Invalid keys {list(yaml_data.keys())} in 'pinout' section. Please remove them."
)
def _pins(
self, yaml_data: Dict[str, Any], name: str, count: int, optional: bool = False
self,
yaml_data: Dict[str, Any],
name: str,
count: int,
errors: List[str],
optional: bool = False,
) -> List[str]:
result: List[str] = []
for i in range(count):
@@ -32,7 +41,9 @@ class PinoutSection:
if pin is None:
if optional and i == 0:
break
raise ProjectYamlError(f"Missing '{name}[{i}]' in 'pinout' section")
errors.append(f"Missing '{name}[{i}]' in 'pinout' section")
# Continue to collect more errors
continue
if pin != "":
self.__nonEmptyPins += 1
result.append(pin)
@@ -45,113 +56,135 @@ class ProjectInfo:
source_files: List[str]
def __init__(self, yaml_data: Dict[str, Any], tile_sizes: Dict[str, str]):
errors: List[str] = []
# Validate Version
yaml_version = yaml_data.get("yaml_version")
if yaml_version is None:
raise ProjectYamlError("Missing 'yaml_version'")
if yaml_version != YAML_VERSION:
raise ProjectYamlError(
errors.append("Missing 'yaml_version'")
elif yaml_version != YAML_VERSION:
errors.append(
f"Unsupported YAML version: {yaml_data['yaml_version']}, expected {YAML_VERSION}"
)
# Read "project" Section
project_section: Optional[Dict[str, Any]] = yaml_data.get("project")
if project_section is None:
raise ProjectYamlError("Missing 'project' section")
errors.append("Missing 'project' section")
# Can't continue without project section
raise ProjectYamlError(errors)
# Validate all required fields in project section
title = project_section.get("title")
if title is None:
raise ProjectYamlError("Missing key 'title' in 'project' section")
if title == "":
raise ProjectYamlError("Project title cannot be empty")
self.title: str = title
errors.append("Missing key 'title' in 'project' section")
elif title == "":
errors.append("Project title cannot be empty")
else:
self.title: str = title
author = project_section.get("author")
if author is None:
raise ProjectYamlError("Missing key 'author' in 'project' section")
if author == "":
raise ProjectYamlError("Project author cannot be empty")
self.author: str = author
errors.append("Missing key 'author' in 'project' section")
elif author == "":
errors.append("Project author cannot be empty")
else:
self.author: str = author
description = project_section.get("description")
if description is None:
raise ProjectYamlError("Missing key 'description' in 'project' section")
if description == "":
raise ProjectYamlError("Project description cannot be empty")
self.description: str = description
errors.append("Missing key 'description' in 'project' section")
elif description == "":
errors.append("Project description cannot be empty")
else:
self.description: str = description
tiles = project_section.get("tiles")
if tiles is None:
raise ProjectYamlError("Missing key 'tiles' in 'project' section")
if tiles not in tile_sizes.keys():
raise ProjectYamlError(
f"Invalid value for 'tiles' in 'project' section: {tiles}"
)
self.tiles: str = tiles
errors.append("Missing key 'tiles' in 'project' section")
elif tiles not in tile_sizes.keys():
errors.append(f"Invalid value for 'tiles' in 'project' section: {tiles}")
else:
self.tiles: str = tiles
analog_pins = project_section.get("analog_pins", 0)
if not isinstance(analog_pins, int):
raise ProjectYamlError(
errors.append(
"Invalid value for 'analog_pins' in 'project' section, must be an integer"
)
if analog_pins < 0 or analog_pins > 6:
raise ProjectYamlError(
analog_pins = 0 # Set default for further validation
elif analog_pins < 0 or analog_pins > 6:
errors.append(
"Invalid value for 'analog_pins' in 'project' section, must be between 0 and 6"
)
analog_pins = 0 # Set default for further validation
self.analog_pins: int = analog_pins
self.is_analog: bool = self.analog_pins > 0
self.uses_3v3: bool = project_section.get("uses_3v3", False)
if self.uses_3v3 and not self.is_analog:
raise ProjectYamlError(
"Projects with 3v3 power need at least one analog pin"
)
uses_3v3: bool = project_section.get("uses_3v3", False)
self.uses_3v3: bool = uses_3v3
if uses_3v3 and not self.is_analog:
errors.append("Projects with 3v3 power need at least one analog pin")
language = project_section.get("language")
if language is None:
raise ProjectYamlError("Missing key 'language' in 'project' section")
if language == "":
raise ProjectYamlError("Project language cannot be empty")
self.language: str = language
errors.append("Missing key 'language' in 'project' section")
language = "" # Set default for further validation
elif language == "":
errors.append("Project language cannot be empty")
else:
self.language: str = language
# Language-specific validation
if language == "Wokwi":
wokwi_id = project_section.get("wokwi_id")
if wokwi_id is None:
raise ProjectYamlError("Missing key 'wokwi_id' in 'project' section")
if wokwi_id == "" or wokwi_id == "0":
raise ProjectYamlError("Please provide a valid Wokwi project ID")
self.wokwi_id: Optional[int] = wokwi_id
self.top_module = f"tt_um_wokwi_{wokwi_id}"
self.source_files = [f"tt_um_wokwi_{self.wokwi_id}.v", f"cells.v"]
else:
errors.append("Missing key 'wokwi_id' in 'project' section")
elif wokwi_id == "" or wokwi_id == "0":
errors.append("Please provide a valid Wokwi project ID")
else:
self.wokwi_id: Optional[int] = wokwi_id
self.top_module = f"tt_um_wokwi_{wokwi_id}"
self.source_files = [f"tt_um_wokwi_{self.wokwi_id}.v", f"cells.v"]
elif language != "":
# Only validate these if language is not empty (error already added above)
top_module = project_section.get("top_module")
if top_module is None:
raise ProjectYamlError("Missing key 'top_module' in 'project' section")
if not top_module.startswith("tt_um_"):
raise ProjectYamlError(
errors.append("Missing key 'top_module' in 'project' section")
elif not top_module.startswith("tt_um_"):
errors.append(
"Top module must start with 'tt_um_' (e.g. tt_um_my_project)"
)
self.top_module = top_module
else:
self.top_module = top_module
if "source_files" not in project_section:
raise ProjectYamlError(
"Missing key 'source_files' in 'project' section"
)
if len(project_section["source_files"]) == 0:
raise ProjectYamlError("No source files specified")
self.source_files = project_section["source_files"]
errors.append("Missing key 'source_files' in 'project' section")
elif len(project_section["source_files"]) == 0:
errors.append("No source files specified")
else:
self.source_files = project_section["source_files"]
# Validate clock_hz
if "clock_hz" not in project_section:
raise ProjectYamlError("Missing key 'clock_hz' in 'project' section")
if not isinstance(project_section["clock_hz"], int):
raise ProjectYamlError(
errors.append("Missing key 'clock_hz' in 'project' section")
elif not isinstance(project_section["clock_hz"], int):
errors.append(
"Invalid value for 'clock_hz' in 'project' section, must be an integer"
)
self.clock_hz: int = project_section["clock_hz"]
else:
self.clock_hz: int = project_section["clock_hz"]
# Validate pinout section
if "pinout" not in yaml_data:
raise ProjectYamlError("Missing 'pinout' section")
self.pinout = PinoutSection(yaml_data["pinout"])
errors.append("Missing 'pinout' section")
else:
self.pinout = PinoutSection(yaml_data["pinout"], errors)
# Optional fields
self.discord: Optional[str] = project_section.get("discord")
self.doc_link: Optional[str] = project_section.get("doc_link")
# Raise all errors if any were collected
if errors:
raise ProjectYamlError(errors)