""" 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 python manage_docs.py build_mcp Optional flags: --docs-dir PATH Path to docs directory (default: ./docs) --package-root NAME Root Python package name (default: mail_intake) """ import re import argparse from pathlib import Path from typing import Iterable 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" DEFAULT_MCP_DIR = PROJECT_ROOT / "mcp" MKDOCSTRINGS_DIRECTIVE = re.compile(r"^:::\s+([a-zA-Z0-9_.]+)", re.MULTILINE) # ------------------------- # Existing functionality # ------------------------- 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 - Creates index.md for packages (__init__.py) - Overwrites content with ::: package.module Examples: mail_intake/__init__.py -> docs/mail_intake/index.md mail_intake/config.py -> docs/mail_intake/config.md mail_intake/adapters/__init__.py -> docs/mail_intake/adapters/index.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"): rel = py_file.relative_to(project_root) if py_file.name == "__init__.py": # Package → index.md module_path = ".".join(rel.parent.parts) md_path = docs_root / rel.parent / "index.md" title = rel.parent.name.replace("_", " ").title() else: # Regular module → .md module_path = ".".join(rel.with_suffix("").parts) md_path = docs_root / rel.with_suffix(".md") title = md_path.stem.replace("_", " ").title() md_path.parent.mkdir(parents=True, exist_ok=True) 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: mkdocs_serve.serve( config_file=str(MKDOCS_YML) ) # ------------------------- # MCP generation # ------------------------- def iter_markdown_files(docs_root: Path) -> Iterable[Path]: yield from docs_root.rglob("*.md") def extract_modules(md_file: Path) -> list[str]: content = md_file.read_text(encoding="utf-8") return MKDOCSTRINGS_DIRECTIVE.findall(content) # ------------------------- # CLI # ------------------------- def main() -> None: parser = argparse.ArgumentParser( prog="manage_docs.py", description="Manage MkDocs documentation and MCP exports 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()