You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

check_md_code_blocks.py 3.9 kB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. """Check code blocks in Markdown files for syntax errors."""
  2. import argparse
  3. import logging
  4. import tempfile
  5. from typing import List, Tuple
  6. from pygments import highlight # type: ignore
  7. from pygments.formatters import TerminalFormatter
  8. from pygments.lexers import PythonLexer
  9. from sphinx.util.console import darkgreen, darkred, faint, red, teal # type: ignore[attr-defined]
  10. logger = logging.getLogger(__name__)
  11. logger.addHandler(logging.StreamHandler())
  12. logger.setLevel(logging.INFO)
  13. def extract_python_code_blocks(markdown_file_path: str) -> List[Tuple[str, int]]:
  14. """Extract Python code blocks from a Markdown file."""
  15. with open(markdown_file_path, "r", encoding="utf-8") as file:
  16. lines = file.readlines()
  17. code_blocks: List[Tuple[str, int]] = []
  18. in_code_block = False
  19. current_block: List[str] = []
  20. for i, line in enumerate(lines):
  21. if line.strip().startswith("```python"):
  22. in_code_block = True
  23. current_block = []
  24. elif line.strip().startswith("```"):
  25. in_code_block = False
  26. code_blocks.append(("\n".join(current_block), i - len(current_block) + 1))
  27. elif in_code_block:
  28. current_block.append(line)
  29. return code_blocks
  30. def check_code_blocks(markdown_file_paths: List[str]) -> None:
  31. """Check Python code blocks in a Markdown file for syntax errors."""
  32. files_with_errors = []
  33. for markdown_file_path in markdown_file_paths:
  34. code_blocks = extract_python_code_blocks(markdown_file_path)
  35. had_errors = False
  36. for code_block, line_no in code_blocks:
  37. markdown_file_path_with_line_no = f"{markdown_file_path}:{line_no}"
  38. logger.info("Checking a code block in %s...", markdown_file_path_with_line_no)
  39. # Skip blocks that don't import autogen_agentchat, autogen_core, or autogen_ext
  40. 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"]):
  41. logger.info(" " + darkgreen("OK[ignored]"))
  42. continue
  43. with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as temp_file:
  44. temp_file.write(code_block.encode("utf-8"))
  45. temp_file.flush()
  46. # Run pyright on the temporary file using subprocess.run
  47. import subprocess
  48. result = subprocess.run(["pyright", temp_file.name], capture_output=True, text=True)
  49. if result.returncode != 0:
  50. logger.info(" " + darkred("FAIL"))
  51. highlighted_code = highlight(code_block, PythonLexer(), TerminalFormatter()) # type: ignore
  52. 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"
  53. logger.info(output)
  54. had_errors = True
  55. else:
  56. logger.info(" " + darkgreen("OK"))
  57. if had_errors:
  58. files_with_errors.append(markdown_file_path)
  59. if files_with_errors:
  60. raise RuntimeError("Syntax errors found in the following files:\n" + "\n".join(files_with_errors))
  61. if __name__ == "__main__":
  62. parser = argparse.ArgumentParser(description="Check code blocks in Markdown files for syntax errors.")
  63. # Argument is a list of markdown files containing glob patterns
  64. parser.add_argument("markdown_files", nargs="+", help="Markdown files to check.")
  65. args = parser.parse_args()
  66. check_code_blocks(args.markdown_files)