diff --git a/manage_docs.py b/manage_docs.py new file mode 100644 index 0000000..e9670ec --- /dev/null +++ b/manage_docs.py @@ -0,0 +1,152 @@ +""" +MkDocs documentation management CLI. + +This script provides a proper CLI interface to: +- Generate MkDocs Markdown files with mkdocstrings directives +- Build the documentation site +- Serve the documentation site locally + +All operations are performed by calling MkDocs as a Python library +(no shell command invocation). + +Requirements: +- mkdocs +- mkdocs-material +- mkdocstrings[python] + +Usage: + python manage_docs.py generate + python manage_docs.py build + python manage_docs.py serve + +Optional flags: + --docs-dir PATH Path to docs directory (default: ./docs) + --package-root NAME Root Python package name (default: mail_intake) +""" + +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + +from mkdocs.commands import build as mkdocs_build +from mkdocs.commands import serve as mkdocs_serve +from mkdocs.config import load_config + + +PROJECT_ROOT = Path(__file__).resolve().parent +DEFAULT_DOCS_DIR = PROJECT_ROOT / "docs" +DEFAULT_PACKAGE_ROOT = "mail_intake" +MKDOCS_YML = PROJECT_ROOT / "mkdocs.yml" + +def generate_docs_from_nav( + project_root: Path, + docs_root: Path, + package_root: str, +) -> None: + """ + Create and populate MkDocs Markdown files with mkdocstrings directives. + + This function: + - Walks the Python package structure + - Mirrors it under the docs directory + - Creates missing .md files + - Overwrites content with ::: package.module + + Example: + mail_intake/config.py -> docs/mail_intake/config.md + mail_intake/adapters/base.py -> docs/mail_intake/adapters/base.md + """ + + package_dir = project_root / package_root + if not package_dir.exists(): + raise FileNotFoundError(f"Package not found: {package_dir}") + + docs_root.mkdir(parents=True, exist_ok=True) + + for py_file in package_dir.rglob("*.py"): + if py_file.name == "__init__.py": + continue + + rel = py_file.relative_to(project_root) + md_path = docs_root / rel.with_suffix(".md") + + md_path.parent.mkdir(parents=True, exist_ok=True) + + module_path = ".".join(rel.with_suffix("").parts) + title = md_path.stem.replace("_", " ").title() + + content = f"""# {title} + +::: {module_path} +""" + + md_path.write_text(content, encoding="utf-8") + + +def load_mkdocs_config(): + if not MKDOCS_YML.exists(): + raise FileNotFoundError("mkdocs.yml not found at project root") + return load_config(str(MKDOCS_YML)) + + +def cmd_generate(args: argparse.Namespace) -> None: + generate_docs_from_nav( + project_root=PROJECT_ROOT, + docs_root=args.docs_dir, + package_root=args.package_root, + ) + + +def cmd_build(_: argparse.Namespace) -> None: + config = load_mkdocs_config() + mkdocs_build.build(config) + + +def cmd_serve(_: argparse.Namespace) -> None: + config = load_mkdocs_config() + mkdocs_serve.serve(config) + + +def main() -> None: + parser = argparse.ArgumentParser( + prog="manage_docs.py", + description="Manage MkDocs documentation for the project", + ) + + parser.add_argument( + "--docs-dir", + type=Path, + default=DEFAULT_DOCS_DIR, + help="Path to the docs directory", + ) + parser.add_argument( + "--package-root", + default=DEFAULT_PACKAGE_ROOT, + help="Root Python package name", + ) + + subparsers = parser.add_subparsers(dest="command", required=True) + + subparsers.add_parser( + "generate", + help="Generate Markdown files with mkdocstrings directives", + ).set_defaults(func=cmd_generate) + + subparsers.add_parser( + "build", + help="Build the MkDocs site", + ).set_defaults(func=cmd_build) + + subparsers.add_parser( + "serve", + help="Serve the MkDocs site locally", + ).set_defaults(func=cmd_serve) + + args = parser.parse_args() + args.func(args) + + +if __name__ == "__main__": + main()