Initial upload: Git AI Documentation Generator v0.1.0
Some checks failed
CI / test (push) Has been cancelled
Some checks failed
CI / test (push) Has been cancelled
This commit is contained in:
156
src/commands/api_docs.py
Normal file
156
src/commands/api_docs.py
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
"""API documentation generation command."""
|
||||||
|
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import click
|
||||||
|
|
||||||
|
from src.config import Config
|
||||||
|
from src.git_utils import (
|
||||||
|
GitError,
|
||||||
|
NotGitRepositoryError,
|
||||||
|
get_file_content_at_ref,
|
||||||
|
get_repo,
|
||||||
|
get_unstaged_diff,
|
||||||
|
)
|
||||||
|
from src.ollama_client import OllamaClient, OllamaConnectionError
|
||||||
|
from src.output import console, display_api_docs, display_error, display_info, display_model_info
|
||||||
|
|
||||||
|
|
||||||
|
LANGUAGE_EXTENSIONS = {
|
||||||
|
".py": "python",
|
||||||
|
".js": "javascript",
|
||||||
|
".ts": "typescript",
|
||||||
|
".go": "go",
|
||||||
|
".rs": "rust",
|
||||||
|
".java": "java",
|
||||||
|
".c": "c",
|
||||||
|
".cpp": "cpp",
|
||||||
|
".h": "c",
|
||||||
|
}
|
||||||
|
|
||||||
|
FRAMEWORK_PATTERNS = {
|
||||||
|
"fastapi": r"from fastapi import|@app\.|FastAPI\(",
|
||||||
|
"flask": r"from flask import|@app\.route|Flask\(",
|
||||||
|
"express": r"express\(\)|@.*route\(|app\.get\(|app\.post\(",
|
||||||
|
"django": r"from django|path\(|urlpatterns",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def detect_language(file_path: str) -> str | None:
|
||||||
|
"""Detect programming language from file extension.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: Path to the file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Language name or None.
|
||||||
|
"""
|
||||||
|
ext = Path(file_path).suffix.lower()
|
||||||
|
return LANGUAGE_EXTENSIONS.get(ext)
|
||||||
|
|
||||||
|
|
||||||
|
def detect_framework(files: list[str]) -> str | None:
|
||||||
|
"""Detect web framework from file contents.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
files: List of file paths to check.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Framework name or None.
|
||||||
|
"""
|
||||||
|
for file_path in files:
|
||||||
|
try:
|
||||||
|
with open(file_path) as f:
|
||||||
|
content = f.read(10000)
|
||||||
|
for framework, pattern in FRAMEWORK_PATTERNS.items():
|
||||||
|
if re.search(pattern, content):
|
||||||
|
return framework
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def find_api_files(files: list[str]) -> list[str]:
|
||||||
|
"""Find files that likely contain API endpoints.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
files: List of file paths.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Filtered list of API-related files.
|
||||||
|
"""
|
||||||
|
api_indicators = ["route", "endpoint", "api", "controller", "handler"]
|
||||||
|
api_files = []
|
||||||
|
for file_path in files:
|
||||||
|
file_lower = file_path.lower()
|
||||||
|
if any(indicator in file_lower for indicator in api_indicators):
|
||||||
|
api_files.append(file_path)
|
||||||
|
return api_files
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option("--staged/--all", default=False, help="Use staged changes or all changes")
|
||||||
|
@click.option("--ref", help="Git reference to compare against")
|
||||||
|
@click.option("--framework", help="Force framework detection")
|
||||||
|
@click.pass_context
|
||||||
|
def api_docs(ctx: click.Context, staged: bool, ref: str | None, framework: str | None) -> None:
|
||||||
|
"""Generate API documentation from code changes."""
|
||||||
|
config: Config = ctx.obj["config"]
|
||||||
|
client = OllamaClient(config)
|
||||||
|
|
||||||
|
try:
|
||||||
|
display_info("Connecting to Ollama...")
|
||||||
|
client.connect_with_retry()
|
||||||
|
display_model_info(config.ollama_host, config.model)
|
||||||
|
except OllamaConnectionError as e:
|
||||||
|
display_error(str(e))
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
repo = get_repo()
|
||||||
|
except NotGitRepositoryError:
|
||||||
|
display_error("Current directory is not a git repository")
|
||||||
|
return
|
||||||
|
|
||||||
|
with console.status("Analyzing code changes..."):
|
||||||
|
if ref:
|
||||||
|
try:
|
||||||
|
changed_files = repo.head.commit.diff(f"{ref}..HEAD")
|
||||||
|
code_changes = {}
|
||||||
|
for change in changed_files:
|
||||||
|
if change.a_path:
|
||||||
|
try:
|
||||||
|
old_content = get_file_content_at_ref(repo, change.a_path, ref)
|
||||||
|
except GitError:
|
||||||
|
old_content = ""
|
||||||
|
new_content = (old_content + "\n" + change.diff.decode("utf-8", errors="replace")
|
||||||
|
if change.diff else old_content)
|
||||||
|
code_changes[change.a_path] = new_content
|
||||||
|
except GitError as e:
|
||||||
|
display_error(str(e))
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
diff_content = get_unstaged_diff(repo)
|
||||||
|
if not diff_content.strip():
|
||||||
|
display_info("No changes found")
|
||||||
|
return
|
||||||
|
changed_files = [f"changed_file_{i}" for i in range(1)]
|
||||||
|
code_changes = {"changed_file.diff": diff_content}
|
||||||
|
|
||||||
|
if not code_changes:
|
||||||
|
display_info("No code changes detected")
|
||||||
|
return
|
||||||
|
|
||||||
|
detected_framework = framework
|
||||||
|
if not detected_framework:
|
||||||
|
api_files = find_api_files(list(code_changes.keys()))
|
||||||
|
if api_files:
|
||||||
|
detected_framework = detect_framework(api_files)
|
||||||
|
|
||||||
|
with console.status("Generating API documentation..."):
|
||||||
|
try:
|
||||||
|
docs = client.generate_api_docs(code_changes, framework=detected_framework)
|
||||||
|
display_api_docs(docs, detected_framework)
|
||||||
|
except OllamaConnectionError as e:
|
||||||
|
display_error(str(e))
|
||||||
Reference in New Issue
Block a user