import json from pathlib import Path import click from src.core.parser import parse_openapi_spec from src.utils.search import create_search_index, search_index from src.utils.templates import generate_html, generate_json, generate_markdown, serve_docs @click.group() def main(): """LocalAPI Docs - Privacy-First OpenAPI Documentation CLI""" pass @main.command("serve") @click.argument("spec_path", type=click.Path(exists=True)) @click.option("--host", default="127.0.0.1", help="Host to bind the server to") @click.option("--port", default=8080, type=int, help="Port to bind the server to") def serve(spec_path: str, host: str, port: int): """Serve interactive API documentation locally""" serve_docs(spec_path, host=host, port=port) @main.command("generate") @click.argument("spec_path", type=click.Path(exists=True)) @click.option("--output", "-o", type=click.Path(), help="Output file path") @click.option( "--format", "fmt", type=click.Choice(["html", "markdown", "json"]), default="html", help="Output format" ) @click.option("--template", type=click.Path(exists=True), help="Custom template file path") def generate(spec_path: str, output: str | None, fmt: str, template: str | None): """Generate documentation in various formats""" if output is None: if fmt == "html": output = "docs.html" elif fmt == "markdown": output = "docs.md" else: output = "docs.json" try: if fmt == "html": generate_html(spec_path, output, template_path=template) elif fmt == "markdown": generate_markdown(spec_path, output, template_path=template) else: generate_json(spec_path, output, template_path=template) click.echo(f"Documentation generated: {output}") except Exception as e: click.echo(f"Error generating documentation: {e}", err=True) @main.command("validate") @click.argument("spec_path", type=click.Path(exists=True)) def validate(spec_path: str): """Validate an OpenAPI specification file""" try: spec = parse_openapi_spec(spec_path) click.echo(f"Valid OpenAPI spec: {spec.info.title} v{spec.info.version}") return True except ValueError as e: click.echo(f"Validation failed: {e}", err=True) return False @main.command("search") @click.argument("spec_path", type=click.Path(exists=True)) @click.argument("query", nargs=-1) def search(spec_path: str, query: tuple): """Search for endpoints in an OpenAPI specification""" query_str = " ".join(query) if not query_str: click.echo("Please provide a search query") return try: try: spec = parse_openapi_spec(spec_path) spec_dict = spec.model_dump() except Exception: content = Path(spec_path).read_text() if spec_path.endswith(('.yaml', '.yml')): import yaml spec_dict = yaml.safe_load(content) else: spec_dict = json.loads(content) index = create_search_index(spec_dict) results = search_index(index, query_str) if results: click.echo(f"Found {len(results)} results for '{query_str}':") for r in results: click.echo(f" [{r.method}] {r.path} - {r.summary or ''}") else: click.echo(f"No results found for '{query_str}'") except Exception as e: click.echo(f"Search failed: {e}", err=True)