"""Check Python code blocks in Quarto (.qmd) files for syntax errors."""
import ast
import sys
from pathlib import Path
import typer
from rich.console import Console
app = typer.Typer()
[docs]
def check_qmd_file(file_path: Path) -> list[str]:
"""Parse a .qmd file, extract Python code blocks, and check their syntax.
:param file_path: Path to the Quarto (.qmd) file to check
:return: List of error messages for code blocks with syntax errors
"""
content: str = file_path.read_text(encoding="utf-8")
code_blocks: list[str] = []
extract_code_blocks(content, code_blocks)
errors: list[str] = []
for i, block in enumerate(code_blocks, 1):
try:
ast.parse(block)
except SyntaxError as e:
error_text = e.text.strip() if e.text else "(no text available)"
errors.append(
f" - Block {i}: SyntaxError on line {e.lineno}: {error_text}"
)
return errors
[docs]
@app.command()
def main(
directory: Path = typer.Argument(
..., help="The directory containing .qmd files to check", exists=True
),
quiet: bool = typer.Option(
False, "--quiet", "-q", help="Only report errors, suppress success messages"
),
) -> None:
"""Check Python code blocks in Quarto (.qmd) files for syntax errors.
Scans all .qmd files in the specified directory, extracts Python code blocks,
and checks them for syntax errors using Python's ast.parse().
Usage:
code_block_checker.py tutorial/
Exit codes:
0: No errors found
1: Syntax errors found or directory invalid
"""
console = Console(stderr=True)
tutorial_dir: Path = directory
qmd_files: list[Path] = list(tutorial_dir.glob("*.qmd"))
total_errors: int = 0
if not quiet:
console.print(
f"[bold cyan]Checking {len(qmd_files)} .qmd files in '{tutorial_dir}'...[/]"
)
for file in qmd_files:
errors: list[str] = check_qmd_file(file)
if errors:
console.print(f"\n[bold red]Found {len(errors)} error(s) in '{file}':[/]")
for error in errors:
console.print(f" [red]{error}[/]")
total_errors += len(errors)
if total_errors > 0:
console.print(f"\n[bold red]Total syntax errors: {total_errors}[/]")
sys.exit(1)
elif not quiet:
console.print(
"\n[bold green]✓ No syntax errors in any tutorial code blocks.[/]"
)
if __name__ == "__main__": # pragma: no cover
app()