Initial upload: Git AI Documentation Generator v0.1.0
Some checks failed
CI / test (push) Has been cancelled

This commit is contained in:
2026-02-01 19:31:04 +00:00
parent 5755ca705c
commit a033bb7c93

156
src/commands/api_docs.py Normal file
View 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))