From 11d369d0539b7681a3e9ef3954c256f4a795aef0 Mon Sep 17 00:00:00 2001 From: Florian Heilmann Date: Fri, 15 Dec 2023 14:23:42 +0100 Subject: [PATCH] fix: Change output format for step results, cleaner logging, separators in comment --- .github/workflows/test_container.yml | 2 +- .github/workflows/test_pr.yml | 2 +- voron_toolkit/constants.py | 10 +-- voron_toolkit/tools/mod_structure_checker.py | 28 ++++++-- voron_toolkit/tools/readme_generator.py | 70 ++++++++++++++----- voron_toolkit/tools/stl_corruption_checker.py | 6 +- voron_toolkit/tools/stl_rotation_checker.py | 8 +-- voron_toolkit/tools/whitespace_checker.py | 5 +- voron_toolkit/utils/pr_helper.py | 4 +- 9 files changed, 93 insertions(+), 42 deletions(-) diff --git a/.github/workflows/test_container.yml b/.github/workflows/test_container.yml index adafc52..ba976b9 100644 --- a/.github/workflows/test_container.yml +++ b/.github/workflows/test_container.yml @@ -63,7 +63,7 @@ jobs: - name: Generate README uses: docker://ghcr.io/fheilmann/voron_toolkit_docker:latest env: - README_GENERATOR_README: true + README_GENERATOR_MARKDOWN: true README_GENERATOR_JSON: true with: args: generate-readme diff --git a/.github/workflows/test_pr.yml b/.github/workflows/test_pr.yml index 408bbdd..32239f1 100644 --- a/.github/workflows/test_pr.yml +++ b/.github/workflows/test_pr.yml @@ -76,7 +76,7 @@ jobs: if: '!cancelled()' uses: docker://ghcr.io/fheilmann/voron_toolkit_docker:latest env: - README_GENERATOR_README: false + README_GENERATOR_MARKDOWN: false README_GENERATOR_JSON: false with: args: generate-readme diff --git a/voron_toolkit/constants.py b/voron_toolkit/constants.py index 1d8d0b4..21367b2 100644 --- a/voron_toolkit/constants.py +++ b/voron_toolkit/constants.py @@ -9,7 +9,7 @@ class StepResultCodeStr(NamedTuple): result_code: int - result_str: str + result_icon: str class StepIdName(NamedTuple): @@ -18,10 +18,10 @@ class StepIdName(NamedTuple): class StepResult(StepResultCodeStr, Enum): - SUCCESS = StepResultCodeStr(result_code=0, result_str="✅ SUCCESS") - WARNING = StepResultCodeStr(result_code=1, result_str="⚠️ WARNING") - FAILURE = StepResultCodeStr(result_code=2, result_str="❌ FAILURE") - EXCEPTION = StepResultCodeStr(result_code=3, result_str="💀 EXCEPTION") + SUCCESS = StepResultCodeStr(result_code=0, result_icon="✅") + WARNING = StepResultCodeStr(result_code=1, result_icon="⚠️") + FAILURE = StepResultCodeStr(result_code=2, result_icon="❌") + EXCEPTION = StepResultCodeStr(result_code=3, result_icon="💀") class StepIdentifier(StepIdName, Enum): diff --git a/voron_toolkit/tools/mod_structure_checker.py b/voron_toolkit/tools/mod_structure_checker.py index 1637794..9378d89 100644 --- a/voron_toolkit/tools/mod_structure_checker.py +++ b/voron_toolkit/tools/mod_structure_checker.py @@ -49,7 +49,11 @@ def _check_mods(self: Self) -> None: if not Path(mod_folder, ".metadata.yml").exists(): logger.error("Mod '{}' is missing a metadata file!", mod_folder_relative) self.check_summary.append( - [mod_folder_relative, StepResult.FAILURE.result_str, FileErrors.mod_missing_metadata.value.format(mod_folder_relative)] + [ + mod_folder_relative, + f"{StepResult.FAILURE.result_icon} {StepResult.FAILURE.name}", + FileErrors.mod_missing_metadata.value.format(mod_folder_relative), + ] ) result = StepResult.FAILURE continue @@ -62,7 +66,7 @@ def _check_mods(self: Self) -> None: self.check_summary.append( [ Path(mod_folder, ".metadata.yml").relative_to(self.input_dir).as_posix(), - StepResult.FAILURE.result_str, + f"{StepResult.FAILURE.result_icon} {StepResult.FAILURE.name}", FileErrors.mod_has_invalid_metadata_file.value.format(mod_folder), ] ) @@ -73,7 +77,7 @@ def _check_mods(self: Self) -> None: self.check_summary.append( [ Path(mod_folder, ".metadata.yml").relative_to(self.input_dir).as_posix(), - StepResult.FAILURE.result_str, + f"{StepResult.FAILURE.result_icon} {StepResult.FAILURE.name}", FileErrors.mod_has_invalid_metadata_file.value.format(mod_folder_relative), ] ) @@ -83,7 +87,11 @@ def _check_mods(self: Self) -> None: if "cad" in metadata and not metadata["cad"]: logger.warning("Mod '{}' has no CAD files!", mod_folder) self.check_summary.append( - [mod_folder_relative, StepResult.FAILURE.result_str, FileErrors.mod_has_no_cad_files.value.format(mod_folder_relative)] + [ + mod_folder_relative, + f"{StepResult.FAILURE.result_icon} {StepResult.FAILURE.name}", + FileErrors.mod_has_no_cad_files.value.format(mod_folder_relative), + ] ) result = StepResult.FAILURE @@ -97,12 +105,12 @@ def _check_mods(self: Self) -> None: self.check_summary.append( [ mod_folder_relative, - StepResult.FAILURE.result_str, + f"{StepResult.FAILURE.result_icon} {StepResult.FAILURE.name}", FileErrors.file_from_metadata_missing.value.format(metadata_file), ] ) result = StepResult.FAILURE - self.check_summary.append([mod_folder_relative, StepResult.SUCCESS.result_str, ""]) + self.check_summary.append([mod_folder_relative, f"{StepResult.SUCCESS.result_icon} {StepResult.SUCCESS.name}", ""]) logger.success("Folder '{}' OK!", mod_folder_relative) self.return_status = result @@ -112,7 +120,13 @@ def _check_shallow_files(self: Self) -> None: result: StepResult = StepResult.SUCCESS for file_folder in files_folders: logger.error("File '{}' outside mod folder structure!", file_folder) - self.check_summary.append([file_folder.relative_to(self.input_dir).as_posix(), FileErrors.file_outside_mod_folder.value.format(file_folder)]) + self.check_summary.append( + [ + file_folder.relative_to(self.input_dir).as_posix(), + f"{StepResult.FAILURE.result_icon} {StepResult.FAILURE.name}", + FileErrors.file_outside_mod_folder.value.format(file_folder), + ] + ) result = StepResult.FAILURE if result == StepResult.SUCCESS: logger.success("Shallow file check OK!") diff --git a/voron_toolkit/tools/readme_generator.py b/voron_toolkit/tools/readme_generator.py index 8354731..1f6249d 100644 --- a/voron_toolkit/tools/readme_generator.py +++ b/voron_toolkit/tools/readme_generator.py @@ -1,14 +1,18 @@ import json import textwrap +from importlib.resources import files from pathlib import Path from typing import Any, Self import configargparse +import jsonschema import yaml from loguru import logger +from voron_toolkit import resources from voron_toolkit.constants import StepIdentifier, StepResult from voron_toolkit.utils.action_summary import ActionSummaryTable +from voron_toolkit.utils.file_helper import FileHelper from voron_toolkit.utils.github_action_helper import ActionResult, GithubActionHelper from voron_toolkit.utils.logging import init_logging @@ -33,30 +37,62 @@ class ReadmeGenerator: def __init__(self: Self, args: configargparse.Namespace) -> None: self.input_dir: Path = Path(Path.cwd(), args.input_dir) self.json: bool = args.json - self.readme: bool = args.readme + self.markdown: bool = args.markdown self.gh_helper: GithubActionHelper = GithubActionHelper(ignore_warnings=False) init_logging(verbose=args.verbose) def run(self: Self) -> None: logger.info("============ README Generator ============") - logger.info("ReadmeGenerator starting up readme: '{}', json: '{}', input_dir: '{}'", self.readme, self.json, self.input_dir.as_posix()) - yaml_list = Path(self.input_dir).glob("**/.metadata.yml") + logger.info("ReadmeGenerator starting up (markdown: '{}', json: '{}', input_dir: '{}')", self.markdown, self.json, self.input_dir.as_posix()) + yaml_list: list[Path] = FileHelper.find_files_by_name(self.input_dir, ".metadata.yml") + schema: dict[str, Any] = json.loads(files(resources).joinpath("voronusers_metadata_schema.json").read_text()) + result: StepResult = StepResult.SUCCESS mods: list[dict[str, Any]] = [] for yml_file in sorted(yaml_list): - logger.info("Parsing '{}'", yml_file.relative_to(self.input_dir).parent.as_posix()) - with Path(yml_file).open("r") as f: - content = yaml.safe_load(f) + mod_path: str = yml_file.relative_to(self.input_dir).parent.as_posix() + try: + metadata: dict[str, Any] = yaml.safe_load(yml_file.read_text()) + jsonschema.validate(instance=metadata, schema=schema) + except (yaml.YAMLError, yaml.scanner.ScannerError) as e: + logger.error("YAML error in metadata file of mod '{}': {}", mod_path, e) + result = StepResult.FAILURE mods.append( { - "path": yml_file.relative_to(self.input_dir).parent.as_posix(), - "title": content["title"], + "path": mod_path, + "title": f"{StepResult.FAILURE.result_icon} Error loading yaml file", "creator": yml_file.relative_to(self.input_dir).parts[0], - "description": content["description"], - "printer_compatibility": f'{", ".join(sorted(content["printer_compatibility"]))}', - "last_changed": GithubActionHelper.last_commit_timestamp(file_or_directory=yml_file.parent), + "description": "", + "printer_compatibility": "", + "last_changed": "", } ) + continue + except jsonschema.ValidationError as e: + logger.error("Validation error in metadata file of mod '{}': {}", mod_path, e.message) + mods.append( + { + "path": mod_path, + "title": f"{StepResult.FAILURE.result_icon} Error validating yaml file", + "creator": yml_file.relative_to(self.input_dir).parts[0], + "description": "", + "printer_compatibility": "", + "last_changed": "", + } + ) + result = StepResult.FAILURE + continue + logger.success("Mod '{}' OK!", mod_path) + mods.append( + { + "path": mod_path, + "title": metadata["title"], + "creator": yml_file.relative_to(self.input_dir).parts[0], + "description": metadata["description"], + "printer_compatibility": f'{", ".join(sorted(metadata["printer_compatibility"]))}', + "last_changed": GithubActionHelper.last_commit_timestamp(file_or_directory=yml_file.parent), + } + ) readme_rows: list[list[str]] = [] prev_username: str = "" @@ -73,11 +109,11 @@ def run(self: Self) -> None: ) prev_username = mod["creator"] - if self.json: + if self.json and (result == StepResult.SUCCESS): logger.info("Writing json file!") self.gh_helper.set_artifact(file_name="mods.json", file_contents=json.dumps(mods, indent=4)) - if self.readme: + if self.markdown and (result == StepResult.SUCCESS): logger.info("Writing README file!") self.gh_helper.set_artifact( file_name="README.md", @@ -91,7 +127,7 @@ def run(self: Self) -> None: action_result=ActionResult( action_id=StepIdentifier.README_GENERATOR.step_id, action_name=StepIdentifier.README_GENERATOR.step_name, - outcome=StepResult.SUCCESS, + outcome=result, summary=ActionSummaryTable( columns=["Creator", "Mod title", "Description", "Printer compatibility", "Last Changed"], rows=readme_rows, @@ -116,11 +152,11 @@ def main() -> None: ) parser.add_argument( "-r", - "--readme", + "--markdown", required=False, action="store_true", - env_var=f"{ENV_VAR_PREFIX}_README", - help="Whether to generate a readme file", + env_var=f"{ENV_VAR_PREFIX}_MARKDOWN", + help="Whether to generate a readme markdown file", default=False, ) parser.add_argument( diff --git a/voron_toolkit/tools/stl_corruption_checker.py b/voron_toolkit/tools/stl_corruption_checker.py index edc605e..bdc42a6 100644 --- a/voron_toolkit/tools/stl_corruption_checker.py +++ b/voron_toolkit/tools/stl_corruption_checker.py @@ -74,18 +74,18 @@ def _check_stl(self: Self, stl_file_path: Path) -> StepResult: number_of_errors: int = sum( int(stl.stats[key]) for key in ["edges_fixed", "backwards_edges", "degenerate_facets", "facets_removed", "facets_added", "facets_reversed"] ) - self.check_summary.append([stl_file_path.name, StepResult.FAILURE.result_str, str(number_of_errors)]) + self.check_summary.append([stl_file_path.name, f"{StepResult.FAILURE.result_icon} {StepResult.FAILURE.name}", str(number_of_errors)]) self._write_fixed_stl_file(stl=stl, path=Path(stl_file_path.relative_to(self.input_dir))) return StepResult.FAILURE logger.success("STL '{}' OK!", stl_file_path.relative_to(self.input_dir).as_posix()) self.check_summary.append( - [stl_file_path.name, StepResult.SUCCESS.result_str, "0"], + [stl_file_path.name, StepResult.SUCCESS.result_icon, "0"], ) return StepResult.SUCCESS except Exception: # noqa: BLE001 logger.critical("A fatal error occurred while checking '{}'!", stl_file_path.relative_to(self.input_dir).as_posix()) self.check_summary.append( - [stl_file_path.name, StepResult.EXCEPTION.result_str, "0"], + [stl_file_path.name, StepResult.EXCEPTION.result_icon, "0"], ) return StepResult.EXCEPTION diff --git a/voron_toolkit/tools/stl_rotation_checker.py b/voron_toolkit/tools/stl_rotation_checker.py index d3e6523..1886e63 100644 --- a/voron_toolkit/tools/stl_rotation_checker.py +++ b/voron_toolkit/tools/stl_rotation_checker.py @@ -136,7 +136,7 @@ def _check_stl(self: Self, stl_file_path: Path) -> StepResult: mesh_objects: dict[int, Any] = FileHandler().load_mesh(inputfile=stl_file_path.as_posix()) if len(mesh_objects.items()) > 1: logger.warning("File '{}' contains multiple objects and is therefore skipped!", stl_file_path.relative_to(self.input_dir).as_posix()) - self.check_summary.append([stl_file_path.name, StepResult.WARNING.result_str, "", ""]) + self.check_summary.append([stl_file_path.name, StepResult.WARNING.result_icon, "", ""]) return StepResult.WARNING rotated_mesh: Tweak = Tweak(mesh_objects[0]["mesh"], extended_mode=True, verbose=False, min_volume=True) @@ -157,7 +157,7 @@ def _check_stl(self: Self, stl_file_path: Path) -> StepResult: self.check_summary.append( [ stl_file_path.name, - StepResult.WARNING.result_str, + StepResult.WARNING.result_icon, original_image_url, rotated_image_url, ], @@ -165,12 +165,12 @@ def _check_stl(self: Self, stl_file_path: Path) -> StepResult: return StepResult.WARNING logger.success("File '{}' OK!", stl_file_path.relative_to(self.input_dir).as_posix()) self.check_summary.append( - [stl_file_path.name, StepResult.SUCCESS.result_str, original_image_url, ""], + [stl_file_path.name, StepResult.SUCCESS.result_icon, original_image_url, ""], ) return StepResult.SUCCESS except Exception: # noqa: BLE001 logger.critical("A fatal error occurred while checking {}", stl_file_path.relative_to(self.input_dir).as_posix()) - self.check_summary.append([stl_file_path.name, StepResult.EXCEPTION.result_str, "", ""]) + self.check_summary.append([stl_file_path.name, StepResult.EXCEPTION.result_icon, "", ""]) return StepResult.EXCEPTION diff --git a/voron_toolkit/tools/whitespace_checker.py b/voron_toolkit/tools/whitespace_checker.py index 6a3a7a4..6643fae 100644 --- a/voron_toolkit/tools/whitespace_checker.py +++ b/voron_toolkit/tools/whitespace_checker.py @@ -36,9 +36,10 @@ def _check_for_whitespace(self: Self) -> None: if result_ok: logger.success("File '{}' OK!", input_file) + self.check_summary.append([input_file, StepResult.SUCCESS.result_icon, ""]) else: logger.error("File '{}' contains whitespace!", input_file) - self.check_summary.append([input_file, "This file contains whitespace!"]) + self.check_summary.append([input_file, StepResult.FAILURE.result_icon, "This [path] contains whitespace!"]) self.return_status = StepResult.FAILURE def run(self: Self) -> None: @@ -61,7 +62,7 @@ def run(self: Self) -> None: action_name=StepIdentifier.WHITESPACE_CHECK.step_name, outcome=self.return_status, summary=ActionSummaryTable( - columns=["File/Folder", "Reason"], + columns=["File/Folder", "Result", "Reason"], rows=self.check_summary, ), ) diff --git a/voron_toolkit/utils/pr_helper.py b/voron_toolkit/utils/pr_helper.py index f34dcd8..8984827 100644 --- a/voron_toolkit/utils/pr_helper.py +++ b/voron_toolkit/utils/pr_helper.py @@ -60,9 +60,9 @@ def _parse_artifact(self: Self) -> None: self.labels.add(CI_ERROR_LABEL) continue outcome: StepResult = StepResult[Path(self.tmp_path, pr_step_identifier.step_id, "outcome.txt").read_text()] - self.comment_body += f"### {pr_step_identifier.step_name}: {outcome.result_str}\n\n" + self.comment_body += f"#### {pr_step_identifier.step_name}: {outcome.result_icon}\n\n" self.comment_body += Path(self.tmp_path, pr_step_identifier.step_id, "summary.md").read_text() - self.comment_body += "\n\n" + self.comment_body += "\n\n---\n\n" if outcome > StepResult.SUCCESS: self.labels.add(CI_FAILURE_LABEL) if not self.labels: