diff --git a/Makefile b/Makefile index 67d37947..933de9df 100644 --- a/Makefile +++ b/Makefile @@ -99,7 +99,7 @@ test-debug: ## runs test cases with debugging info enabled test-cov: ## checks test coverage requirements $(PYTHON3) -m pytest -n auto --cov-config=.coveragerc --cov=$(SRC_DIR) \ - $(TEST_DIR) --cov-fail-under=45 --cov-report term-missing + $(TEST_DIR) --cov-fail-under=80 --cov-report term-missing lint: ## runs the linter against the project pylint --rcfile=.pylintrc $(SRC_DIR) $(TEST_DIR) diff --git a/conda_recipe_manager/commands/convert.py b/conda_recipe_manager/commands/convert.py index f4fc2148..6564f2bd 100644 --- a/conda_recipe_manager/commands/convert.py +++ b/conda_recipe_manager/commands/convert.py @@ -100,13 +100,14 @@ def _record_unrecoverable_failure( return conversion_result -def convert_file(file_path: Path, output: Optional[Path], print_output: bool) -> ConversionResult: +def convert_file(file_path: Path, output: Optional[Path], print_output: bool, debug: bool) -> ConversionResult: """ Converts a single recipe file to the new format, tracking results. :param file_path: Path to the recipe file to convert :param output: If specified, the file contents are written to this file path. Otherwise, the file is dumped to STDOUT IF `print_output` is set to `True`. :param print_output: Prints the recipe to STDOUT/STDERR if the output file is not specified and this flag is `True`. + :param debug: Enables debug mode output. Prints to STDERR. :returns: A struct containing the results of the conversion process, including debugging metadata. """ conversion_result = ConversionResult( @@ -146,9 +147,16 @@ def convert_file(file_path: Path, output: Optional[Path], print_output: bool) -> e, ) + # Print the initial parser, if requested + print_err("########## PARSED RECIPE FILE ##########", print_enabled=debug) + print_err(parser, print_enabled=debug) + # Convert the recipe try: - conversion_result.content, conversion_result.msg_tbl = parser.render_to_new_recipe_format() + conversion_result.content, conversion_result.msg_tbl, debug_new_parser = parser.render_to_new_recipe_format() + # Print the new parser, if requested + print_err("########## CONVERTED RECIPE FILE ##########", print_enabled=debug) + print_err(debug_new_parser, print_enabled=debug) except Exception as e: # pylint: disable=broad-exception-caught return _record_unrecoverable_failure( conversion_result, @@ -172,16 +180,17 @@ def convert_file(file_path: Path, output: Optional[Path], print_output: bool) -> return conversion_result -def process_recipe(file: Path, path: Path, output: Optional[Path]) -> tuple[str, ConversionResult]: +def process_recipe(file: Path, path: Path, output: Optional[Path], debug: bool) -> tuple[str, ConversionResult]: """ Helper function that performs the conversion operation for parallelizable execution. :param file: Recipe file to convert :param path: Path argument provided by the user :param output: Output argument file provided by the user + :param debug: Enables debug mode output. Prints to STDERR. :returns: Tuple containing the key/value pairing that tracks the result of the conversion operation """ out_file: Optional[Path] = None if output is None else file.parent / output - conversion_result = convert_file(file, out_file, False) + conversion_result = convert_file(file, out_file, False, debug) conversion_result.project_name = file.relative_to(path).parts[0] return str(file.relative_to(path)), conversion_result @@ -251,8 +260,14 @@ def _collect_issue_stats(project_name: str, issues: list[str], hist: dict[str, i is_flag=True, help="Truncates logging. On large tests in a GitHub CI environment, this can eliminate log buffering issues.", ) +@click.option( + "--debug", + "-d", + is_flag=True, + help="Debug mode, prints debugging information to STDERR.", +) def convert( - path: Path, output: Optional[Path], min_success_rate: float, truncate: bool + path: Path, output: Optional[Path], min_success_rate: float, truncate: bool, debug: bool ) -> None: # pylint: disable=redefined-outer-name """ Recipe conversion CLI utility. By default, recipes print to STDOUT. Messages always print to STDERR. Takes 1 file or @@ -265,7 +280,7 @@ def convert( ## Single-file case ## if len(files) == 1: - result: Final[ConversionResult] = convert_file(files[0], output, True) + result: Final[ConversionResult] = convert_file(files[0], output, True, debug) print_messages(MessageCategory.WARNING, result.msg_tbl) print_messages(MessageCategory.ERROR, result.msg_tbl) print_err(result.msg_tbl.get_totals_message()) @@ -276,7 +291,9 @@ def convert( # Process recipes in parallel thread_pool_size: Final[int] = mp.cpu_count() with mp.Pool(thread_pool_size) as pool: - results = dict(pool.starmap(process_recipe, [(file, path, output) for file in files])) # type: ignore[misc] + results = dict( + pool.starmap(process_recipe, [(file, path, output, debug) for file in files]) # type: ignore[misc] + ) # Tracking failures from bulk operation recipes_with_except: list[str] = [] diff --git a/conda_recipe_manager/parser/recipe_parser_convert.py b/conda_recipe_manager/parser/recipe_parser_convert.py index 11c157b2..41c99326 100644 --- a/conda_recipe_manager/parser/recipe_parser_convert.py +++ b/conda_recipe_manager/parser/recipe_parser_convert.py @@ -437,7 +437,7 @@ def pre_process_recipe_text(content: str) -> str: return content - def render_to_new_recipe_format(self) -> tuple[str, MessageTable]: + def render_to_new_recipe_format(self) -> tuple[str, MessageTable, RecipeParser]: """ Takes the current recipe representation and renders it to the new format WITHOUT modifying the current recipe state. @@ -447,6 +447,10 @@ def render_to_new_recipe_format(self) -> tuple[str, MessageTable]: - https://github.com/conda-incubator/ceps/blob/main/cep-14.md (As of writing there is no official name other than "the new recipe format") + :returns: Returns a tuple containing: + - The converted recipe, as a string + - A `MessageTbl` instance that contains error logging + - The `RecipeParser` instance containing the converted recipe file. USE FOR DEBUGGING PURPOSES ONLY! """ # Approach: In the event that we want to expand support later, this function should be implemented in terms # of a `RecipeParser` tree. This will make it easier to build an upgrade-path, if we so choose to pursue one. @@ -496,4 +500,4 @@ def render_to_new_recipe_format(self) -> tuple[str, MessageTable]: # "sensible" to a human reader. self._sort_subtree_keys("/", TOP_LEVEL_KEY_SORT_ORDER) - return self._new_recipe.render(), self._msg_tbl + return self._new_recipe.render(), self._msg_tbl, self._new_recipe diff --git a/tests/parser/test_recipe_parser_convert.py b/tests/parser/test_recipe_parser_convert.py index 175c1159..b1f7d30c 100644 --- a/tests/parser/test_recipe_parser_convert.py +++ b/tests/parser/test_recipe_parser_convert.py @@ -120,7 +120,7 @@ def test_render_to_new_recipe_format(file_base: str, errors: list[str], warnings :param file_base: Base file name for both the input and the expected out """ parser = load_recipe_convert(file_base) - result, tbl = parser.render_to_new_recipe_format() + result, tbl, _ = parser.render_to_new_recipe_format() assert result == load_file(f"{TEST_FILES_PATH}/new_format_{file_base}") assert tbl.get_messages(MessageCategory.ERROR) == errors assert tbl.get_messages(MessageCategory.WARNING) == warnings