|
| 1 | +#!/usr/bin/env python3 |
| 2 | +"""Generate a Markdown file for each Python example. |
| 3 | +
|
| 4 | +Usage: |
| 5 | + python .scripts/examples_to_markdown_files.py \ |
| 6 | + --examples-dir examples \ |
| 7 | + --template templates/example_file.mustache \ |
| 8 | + --output-dir docs |
| 9 | +
|
| 10 | +The directory structure under ``examples`` is mirrored under the output |
| 11 | +directory. Each ``.py`` file becomes a ``.md`` file with the same name and |
| 12 | +relative path. Files named ``__init__.py`` are ignored and skipped. |
| 13 | +""" |
| 14 | + |
| 15 | +import argparse |
| 16 | +from pathlib import Path |
| 17 | + |
| 18 | +try: |
| 19 | + import pystache |
| 20 | +except ModuleNotFoundError as exc: # pragma: no cover - import guard |
| 21 | + raise SystemExit( |
| 22 | + "pystache is required to run this script. Install it via `pip install pystache`." |
| 23 | + ) from exc |
| 24 | + |
| 25 | + |
| 26 | +def parse_example(path: Path) -> dict: |
| 27 | + """Return the file name and entire contents of the example.""" |
| 28 | + with path.open("r", encoding="utf-8") as f: |
| 29 | + code = f.read() |
| 30 | + return {"name": path.stem, "code": code.rstrip()} |
| 31 | + |
| 32 | + |
| 33 | +def render_markdown(template: str, context: dict) -> str: |
| 34 | + """Render the Markdown using a Mustache template.""" |
| 35 | + renderer = pystache.Renderer() |
| 36 | + return renderer.render(template, context) |
| 37 | + |
| 38 | + |
| 39 | +def process_file(py_path: Path, template: str, base_dir: Path, output_dir: Path) -> None: |
| 40 | + """Write a Markdown version of *py_path* under *output_dir*.""" |
| 41 | + context = parse_example(py_path) |
| 42 | + markdown = render_markdown(template, context) |
| 43 | + |
| 44 | + relative = py_path.relative_to(base_dir).with_suffix(".md") |
| 45 | + md_path = output_dir / relative |
| 46 | + md_path.parent.mkdir(parents=True, exist_ok=True) |
| 47 | + with md_path.open("w", encoding="utf-8") as f: |
| 48 | + f.write(markdown) |
| 49 | + |
| 50 | + |
| 51 | +def main() -> None: |
| 52 | + parser = argparse.ArgumentParser( |
| 53 | + description="Generate Markdown files mirroring the examples directory" |
| 54 | + ) |
| 55 | + parser.add_argument( |
| 56 | + "--examples-dir", |
| 57 | + default="examples", |
| 58 | + help="Directory containing example .py files", |
| 59 | + ) |
| 60 | + parser.add_argument( |
| 61 | + "--template", |
| 62 | + default="templates/example_file.mustache", |
| 63 | + help="Mustache template file", |
| 64 | + ) |
| 65 | + parser.add_argument( |
| 66 | + "--output-dir", |
| 67 | + default="docs", |
| 68 | + help="Directory where Markdown files will be written", |
| 69 | + ) |
| 70 | + args = parser.parse_args() |
| 71 | + |
| 72 | + examples_dir = Path(args.examples_dir) |
| 73 | + output_dir = Path(args.output_dir) |
| 74 | + # Ensure the output directory exists but do not wipe it if it already |
| 75 | + # contains files. ``exist_ok=True`` prevents accidental deletion of |
| 76 | + # previously generated documentation. |
| 77 | + output_dir.mkdir(parents=True, exist_ok=True) |
| 78 | + |
| 79 | + with Path(args.template).open("r", encoding="utf-8") as f: |
| 80 | + template = f.read() |
| 81 | + |
| 82 | + for py_file in sorted(examples_dir.rglob("*.py")): |
| 83 | + if py_file.name == "__init__.py": |
| 84 | + continue |
| 85 | + process_file(py_file, template, examples_dir, output_dir) |
| 86 | + |
| 87 | + |
| 88 | +if __name__ == "__main__": |
| 89 | + main() |
0 commit comments