"""Check code blocks in Markdown files for syntax errors.""" import argparse import logging import tempfile from typing import List, Tuple from pygments import highlight # type: ignore from pygments.formatters import TerminalFormatter from pygments.lexers import PythonLexer from sphinx.util.console import darkgreen, darkred, faint, red, teal # type: ignore[attr-defined] logger = logging.getLogger(__name__) logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.INFO) def extract_python_code_blocks(markdown_file_path: str) -> List[Tuple[str, int]]: """Extract Python code blocks from a Markdown file.""" with open(markdown_file_path, "r", encoding="utf-8") as file: lines = file.readlines() code_blocks: List[Tuple[str, int]] = [] in_code_block = False current_block: List[str] = [] for i, line in enumerate(lines): if line.strip().startswith("```python"): in_code_block = True current_block = [] elif line.strip().startswith("```"): in_code_block = False code_blocks.append(("\n".join(current_block), i - len(current_block) + 1)) elif in_code_block: current_block.append(line) return code_blocks def check_code_blocks(markdown_file_paths: List[str]) -> None: """Check Python code blocks in a Markdown file for syntax errors.""" files_with_errors = [] for markdown_file_path in markdown_file_paths: code_blocks = extract_python_code_blocks(markdown_file_path) had_errors = False for code_block, line_no in code_blocks: markdown_file_path_with_line_no = f"{markdown_file_path}:{line_no}" logger.info("Checking a code block in %s...", markdown_file_path_with_line_no) # Skip blocks that don't import autogen_agentchat, autogen_core, or autogen_ext if all(all(import_code not in code_block for import_code in [f"import {module}", f"from {module}"]) for module in ["autogen_agentchat", "autogen_core", "autogen_ext"]): logger.info(" " + darkgreen("OK[ignored]")) continue with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as temp_file: temp_file.write(code_block.encode("utf-8")) temp_file.flush() # Run pyright on the temporary file using subprocess.run import subprocess result = subprocess.run(["pyright", temp_file.name], capture_output=True, text=True) if result.returncode != 0: logger.info(" " + darkred("FAIL")) highlighted_code = highlight(code_block, PythonLexer(), TerminalFormatter()) # type: ignore output = f"{faint('========================================================')}\n{red('Error')}: Pyright found issues in {teal(markdown_file_path_with_line_no)}:\n{faint('--------------------------------------------------------')}\n{highlighted_code}\n{faint('--------------------------------------------------------')}\n\n{teal('pyright output:')}\n{red(result.stdout)}{faint('========================================================')}\n" logger.info(output) had_errors = True else: logger.info(" " + darkgreen("OK")) if had_errors: files_with_errors.append(markdown_file_path) if files_with_errors: raise RuntimeError("Syntax errors found in the following files:\n" + "\n".join(files_with_errors)) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Check code blocks in Markdown files for syntax errors.") # Argument is a list of markdown files containing glob patterns parser.add_argument("markdown_files", nargs="+", help="Markdown files to check.") args = parser.parse_args() check_code_blocks(args.markdown_files)