""" # Summary Command definitions for the doc-forge CLI. Provides the CLI structure using Click, including build, serve, and tree commands. """ import click from pathlib import Path from typing import Sequence, Optional from docforge.loaders import GriffeLoader from docforge.cli import mkdocs_utils from docforge.cli import mcp_utils @click.group() def cli() -> None: """ Root command group for the doc-forge CLI. Provides commands for building, serving, and inspecting documentation generated from Python source code. """ pass @cli.command() @click.option("--mcp", is_flag=True, help="Build MCP resources") @click.option("--mkdocs", is_flag=True, help="Build MkDocs site") @click.option("--module-is-source", is_flag=True, help="Module is source folder and to be treated as root folder") @click.option("--module", help="Python module to document") @click.option("--project-name", help="Project name override") @click.option("--site-name", help="MkDocs site name") @click.option("--docs-dir", type=click.Path(path_type=Path), default=Path("docs"), help="Directory for MD sources") @click.option("--nav", "nav_file", type=click.Path(path_type=Path), default=Path("docforge.nav.yml"), help="Nav spec path") @click.option("--template", type=click.Path(path_type=Path), help="MkDocs template path") @click.option("--mkdocs-yml", type=click.Path(path_type=Path), default=Path("mkdocs.yml"), help="Output config path") @click.option("--out-dir", type=click.Path(path_type=Path), default=Path("mcp_docs"), help="MCP output directory") def build( mcp: bool, mkdocs: bool, module_is_source: bool, module: Optional[str], project_name: Optional[str], site_name: Optional[str], docs_dir: Path, nav_file: Path, template: Optional[Path], mkdocs_yml: Path, out_dir: Path, ) -> None: """ Build documentation artifacts. This command performs the full documentation build pipeline: 1. Introspects the Python project using Griffe 2. Generates renderer-specific documentation sources 3. Optionally builds the final documentation output Depending on the selected options, the build can target: - MkDocs static documentation sites - MCP structured documentation resources Args: mcp (bool): Enable MCP documentation generation. mkdocs (bool): Enable MkDocs documentation generation. module_is_source (bool): Treat the specified module directory as the project root. module (Optional[str]): Python module import path to document. project_name (Optional[str]): Optional override for the project name. site_name (Optional[str]): Display name for the MkDocs site. docs_dir (Path): Directory where Markdown documentation sources will be generated. nav_file (Path): Path to the navigation specification file. template (Optional[Path]): Optional custom MkDocs configuration template. mkdocs_yml (Path): Output path for the generated MkDocs configuration. out_dir (Path): Output directory for generated MCP resources. Raises: click.UsageError: If required options are missing or conflicting. """ if not mcp and not mkdocs: raise click.UsageError("Must specify either --mcp or --mkdocs") if mkdocs: if not module: raise click.UsageError("--module is required for MkDocs build") if not site_name: site_name = module click.echo(f"Generating MkDocs sources in {docs_dir}...") mkdocs_utils.generate_sources( module, docs_dir, project_name, module_is_source, ) click.echo(f"Generating MkDocs config {mkdocs_yml}...") mkdocs_utils.generate_config(docs_dir, nav_file, template, mkdocs_yml, site_name) click.echo("Running MkDocs build...") mkdocs_utils.build(mkdocs_yml) click.echo("MkDocs build completed.") if mcp: if not module: raise click.UsageError("--module is required for MCP build") click.echo(f"Generating MCP resources in {out_dir}...") mcp_utils.generate_resources(module, project_name, out_dir) click.echo("MCP build completed.") @cli.command() @click.option("--mcp", is_flag=True, help="Serve MCP documentation") @click.option("--mkdocs", is_flag=True, help="Serve MkDocs site") @click.option("--module", help="Python module to serve") @click.option("--mkdocs-yml", type=click.Path(path_type=Path), default=Path("mkdocs.yml"), help="MkDocs config path") @click.option("--out-dir", type=click.Path(path_type=Path), default=Path("mcp_docs"), help="MCP root directory") def serve( mcp: bool, mkdocs: bool, module: Optional[str], mkdocs_yml: Path, out_dir: Path, ) -> None: """ Serve generated documentation locally. Depending on the selected mode, this command starts either: - A MkDocs development server for browsing documentation - An MCP server exposing structured documentation resources Args: mcp (bool): Serve documentation using the MCP server. mkdocs (bool): Serve the MkDocs development site. module (Optional[str]): Python module import path to serve via MCP. mkdocs_yml (Path): Path to the MkDocs configuration file. out_dir (Path): Root directory containing MCP documentation resources. Raises: click.UsageError: If invalid or conflicting options are provided. """ if mcp and mkdocs: raise click.UsageError("Cannot specify both --mcp and --mkdocs") if not mcp and not mkdocs: raise click.UsageError("Must specify either --mcp or --mkdocs") if mcp and not module: raise click.UsageError("--module is required for MCP serve") if mkdocs: mkdocs_utils.serve(mkdocs_yml) elif mcp: mcp_utils.serve(module, out_dir) @cli.command() @click.option( "--module", required=True, help="Python module import path to introspect", ) @click.option( "--project-name", help="Project name (defaults to specified module)", ) def tree( module: str, project_name: Optional[str], ) -> None: """ Display the documentation object tree for a module. This command introspects the specified module and prints a hierarchical representation of the discovered documentation objects, including modules, classes, functions, and members. Args: module (str): Python module import path to introspect. project_name (Optional[str]): Optional name to display as the project root. """ loader = GriffeLoader() project = loader.load_project([module], project_name) click.echo(project.name) for module in project.get_all_modules(): click.echo(f"├── {module.path}") for obj in module.get_all_objects(): _print_object(obj, indent="│ ") def _print_object(obj, indent: str) -> None: """ Recursively print a documentation object and its members. This helper function traverses the documentation object graph and prints each object with indentation to represent hierarchy. Args: obj: Documentation object to print. indent (str): Current indentation prefix used for nested members. """ click.echo(f"{indent}├── {obj.name}") for member in obj.get_all_members(): _print_object(member, indent + "│ ")