cleanup code
This commit is contained in:
@@ -1,333 +0,0 @@
|
||||
"""Command-line interface for doc-forge.
|
||||
|
||||
The CLI provides a thin orchestration layer over the core doc-forge
|
||||
functionality. It follows the ADS specification by being library-first
|
||||
with the CLI as a secondary interface.
|
||||
|
||||
Available commands:
|
||||
- generate: Generate source files for a renderer
|
||||
- build: Build final documentation artifacts
|
||||
- serve: Serve documentation locally
|
||||
- export: Export to MCP format
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
try:
|
||||
import click
|
||||
except ImportError as e:
|
||||
raise ImportError(
|
||||
"click is required for doc-forge CLI. "
|
||||
"Install with: pip install click"
|
||||
) from e
|
||||
|
||||
from docforge.loader import GriffeLoader
|
||||
from docforge.renderers import MkDocsRenderer, SphinxRenderer
|
||||
from docforge.exporters import MCPExporter
|
||||
from docforge.server import MCPServer
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose logging")
|
||||
@click.pass_context
|
||||
def cli(ctx: click.Context, verbose: bool) -> None:
|
||||
"""doc-forge — A renderer-agnostic Python documentation compiler.
|
||||
|
||||
doc-forge converts Python source code and docstrings into a structured,
|
||||
semantic documentation model and emits multiple downstream representations.
|
||||
"""
|
||||
if verbose:
|
||||
logging.basicConfig(level=logging.DEBUG, format="%(levelname)s: %(message)s")
|
||||
else:
|
||||
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
|
||||
|
||||
# Ensure context object exists
|
||||
ctx.ensure_object(dict)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option("--renderer", type=click.Choice(["mkdocs", "sphinx"]), required=True, help="Renderer to use")
|
||||
@click.option("--out-dir", type=click.Path(path_type=Path), default=Path("docs"), help="Output directory")
|
||||
@click.option("--project-name", help="Project name (defaults to first module)")
|
||||
@click.argument("modules", nargs=-1, required=True, help="Python modules to document")
|
||||
@click.pass_context
|
||||
def generate(
|
||||
ctx: click.Context,
|
||||
renderer: str,
|
||||
out_dir: Path,
|
||||
project_name: Optional[str],
|
||||
modules: List[str],
|
||||
) -> None:
|
||||
"""Generate documentation source files.
|
||||
|
||||
Generate renderer-specific source files from Python modules.
|
||||
This creates the source files but does not build the final documentation.
|
||||
|
||||
Examples:
|
||||
doc-forge generate --renderer mkdocs mypackage
|
||||
doc-forge generate --renderer sphinx mypackage.submodule mypackage.utils
|
||||
"""
|
||||
try:
|
||||
# Load project
|
||||
loader = GriffeLoader()
|
||||
project = loader.load_project(list(modules), project_name)
|
||||
|
||||
# Get renderer
|
||||
renderer_instance = _get_renderer(renderer)
|
||||
|
||||
# Generate sources
|
||||
renderer_instance.generate_sources(project, out_dir)
|
||||
|
||||
click.echo(f"Generated {renderer} sources in {out_dir}")
|
||||
click.echo(f"Modules: {', '.join(modules)}")
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"Error: {e}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option("--renderer", type=click.Choice(["mkdocs", "sphinx"]), required=True, help="Renderer to use")
|
||||
@click.option("--out-dir", type=click.Path(path_type=Path), default=Path("docs"), help="Output directory")
|
||||
@click.option("--project-name", help="Project name (defaults to first module)")
|
||||
@click.argument("modules", nargs=-1, required=True, help="Python modules to document")
|
||||
@click.pass_context
|
||||
def build(
|
||||
ctx: click.Context,
|
||||
renderer: str,
|
||||
out_dir: Path,
|
||||
project_name: Optional[str],
|
||||
modules: List[str],
|
||||
) -> None:
|
||||
"""Build final documentation artifacts.
|
||||
|
||||
Build the final documentation from Python modules. This both generates
|
||||
source files and builds the final artifacts (HTML, etc.).
|
||||
|
||||
Examples:
|
||||
doc-forge build --renderer mkdocs mypackage
|
||||
doc-forge build --renderer sphinx mypackage.submodule
|
||||
"""
|
||||
try:
|
||||
# Load project
|
||||
loader = GriffeLoader()
|
||||
project = loader.load_project(list(modules), project_name)
|
||||
|
||||
# Get renderer
|
||||
renderer_instance = _get_renderer(renderer)
|
||||
|
||||
# Generate sources first
|
||||
renderer_instance.generate_sources(project, out_dir)
|
||||
|
||||
# Build final artifacts
|
||||
from docforge.renderers.base import RendererConfig
|
||||
config = RendererConfig(out_dir, project)
|
||||
renderer_instance.build(config)
|
||||
|
||||
click.echo(f"Built {renderer} documentation in {out_dir}")
|
||||
|
||||
# Show output location
|
||||
if renderer == "mkdocs":
|
||||
build_dir = out_dir / "_site"
|
||||
else: # sphinx
|
||||
build_dir = out_dir / "build" / "html"
|
||||
|
||||
if build_dir.exists():
|
||||
click.echo(f"Output: {build_dir}")
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"Error: {e}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option("--renderer", type=click.Choice(["mkdocs", "sphinx"]), required=True, help="Renderer to use")
|
||||
@click.option("--out-dir", type=click.Path(path_type=Path), default=Path("docs"), help="Output directory")
|
||||
@click.option("--host", default="127.0.0.1", help="Host to serve on")
|
||||
@click.option("--port", type=int, default=8000, help="Port to serve on")
|
||||
@click.option("--project-name", help="Project name (defaults to first module)")
|
||||
@click.argument("modules", nargs=-1, required=True, help="Python modules to document")
|
||||
@click.pass_context
|
||||
def serve(
|
||||
ctx: click.Context,
|
||||
renderer: str,
|
||||
out_dir: Path,
|
||||
host: str,
|
||||
port: int,
|
||||
project_name: Optional[str],
|
||||
modules: List[str],
|
||||
) -> None:
|
||||
"""Serve documentation locally.
|
||||
|
||||
Start a local development server to serve documentation.
|
||||
This generates sources, builds artifacts, and starts a server.
|
||||
|
||||
Examples:
|
||||
doc-forge serve --renderer mkdocs mypackage
|
||||
doc-forge serve --renderer sphinx --port 9000 mypackage
|
||||
"""
|
||||
try:
|
||||
# Load project
|
||||
loader = GriffeLoader()
|
||||
project = loader.load_project(list(modules), project_name)
|
||||
|
||||
# Get renderer
|
||||
renderer_instance = _get_renderer(renderer)
|
||||
|
||||
# Generate sources first
|
||||
renderer_instance.generate_sources(project, out_dir)
|
||||
|
||||
# Build artifacts
|
||||
from docforge.renderers.base import RendererConfig
|
||||
config = RendererConfig(out_dir, project, {"host": host, "port": port})
|
||||
|
||||
# Start serving
|
||||
click.echo(f"Serving {renderer} documentation at http://{host}:{port}")
|
||||
click.echo("Press Ctrl+C to stop serving")
|
||||
|
||||
renderer_instance.serve(config)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
click.echo("\nStopped serving")
|
||||
except Exception as e:
|
||||
click.echo(f"Error: {e}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option("--out-dir", type=click.Path(path_type=Path), default=Path("mcp"), help="Output directory")
|
||||
@click.option("--project-name", help="Project name (defaults to first module)")
|
||||
@click.argument("modules", nargs=-1, required=True, help="Python modules to document")
|
||||
@click.pass_context
|
||||
def export(
|
||||
ctx: click.Context,
|
||||
out_dir: Path,
|
||||
project_name: Optional[str],
|
||||
modules: List[str],
|
||||
) -> None:
|
||||
"""Export documentation to MCP format.
|
||||
|
||||
Export documentation as a static MCP JSON bundle. This creates
|
||||
machine-consumable documentation that can be loaded by MCP clients.
|
||||
|
||||
Examples:
|
||||
doc-forge export mypackage
|
||||
doc-forge export --out-dir ./docs/mcp mypackage.submodule
|
||||
"""
|
||||
try:
|
||||
# Load project
|
||||
loader = GriffeLoader()
|
||||
project = loader.load_project(list(modules), project_name)
|
||||
|
||||
# Export to MCP
|
||||
exporter = MCPExporter()
|
||||
exporter.export(project, out_dir)
|
||||
|
||||
click.echo(f"Exported MCP bundle to {out_dir}")
|
||||
click.echo(f"Modules: {', '.join(modules)}")
|
||||
|
||||
# Show bundle structure
|
||||
index_path = out_dir / "index.json"
|
||||
nav_path = out_dir / "nav.json"
|
||||
modules_dir = out_dir / "modules"
|
||||
|
||||
click.echo(f"Bundle structure:")
|
||||
click.echo(f" {index_path}")
|
||||
click.echo(f" {nav_path}")
|
||||
click.echo(f" {modules_dir}/")
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"Error: {e}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option("--host", default="127.0.0.1", help="Host to serve on")
|
||||
@click.option("--port", type=int, default=8080, help="Port to serve on")
|
||||
@click.option("--project-name", help="Project name (defaults to first module)")
|
||||
@click.argument("modules", nargs=-1, required=True, help="Python modules to document")
|
||||
@click.pass_context
|
||||
def server(
|
||||
ctx: click.Context,
|
||||
host: str,
|
||||
port: int,
|
||||
project_name: Optional[str],
|
||||
modules: List[str],
|
||||
) -> None:
|
||||
"""Start MCP server for live documentation access.
|
||||
|
||||
Start a live MCP server that provides real-time access to documentation
|
||||
through the Model Context Protocol. This serves documentation directly
|
||||
from the Python model without generating static files.
|
||||
|
||||
Examples:
|
||||
doc-forge server mypackage
|
||||
doc-forge server --port 9000 mypackage.submodule
|
||||
"""
|
||||
try:
|
||||
# Load project
|
||||
loader = GriffeLoader()
|
||||
project = loader.load_project(list(modules), project_name)
|
||||
|
||||
# Start MCP server
|
||||
server = MCPServer(project)
|
||||
|
||||
click.echo(f"Starting MCP server at http://{host}:{port}")
|
||||
click.echo("Available resources:")
|
||||
click.echo(f" docs://index - Project metadata")
|
||||
click.echo(f" docs://nav - Navigation structure")
|
||||
for module_path in project.get_module_list():
|
||||
click.echo(f" docs://module/{module_path} - Module documentation")
|
||||
click.echo("Press Ctrl+C to stop server")
|
||||
|
||||
server.start(host, port)
|
||||
|
||||
# Keep server running
|
||||
try:
|
||||
while server.is_running():
|
||||
import time
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
click.echo("\nStopping server...")
|
||||
server.stop()
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"Error: {e}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _get_renderer(renderer_name: str):
|
||||
"""Get renderer instance by name.
|
||||
|
||||
Args:
|
||||
renderer_name: Name of the renderer
|
||||
|
||||
Returns:
|
||||
Renderer instance
|
||||
|
||||
Raises:
|
||||
ValueError: If renderer name is unknown
|
||||
"""
|
||||
if renderer_name == "mkdocs":
|
||||
return MkDocsRenderer()
|
||||
elif renderer_name == "sphinx":
|
||||
return SphinxRenderer()
|
||||
else:
|
||||
raise ValueError(f"Unknown renderer: {renderer_name}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Main entry point for the CLI."""
|
||||
cli()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user