98 lines
3.4 KiB
Python
98 lines
3.4 KiB
Python
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)
|