diff --git a/src/docgen/detectors/go.py b/src/docgen/detectors/go.py new file mode 100644 index 0000000..5db4eea --- /dev/null +++ b/src/docgen/detectors/go.py @@ -0,0 +1,119 @@ +"""Go endpoint detector for Gin and chi.""" + +import re +from pathlib import Path +from typing import Optional +from docgen.models import Endpoint, HTTPMethod +from docgen.detectors.base import BaseDetector + + +class GoDetector(BaseDetector): + """Detector for Go web frameworks.""" + + extensions = [".go"] + framework_name = "go" + + GIN_PATTERN = re.compile( + r'(?:r\.router|r\.Group|routers?)\s*\.\s*(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\s*\(\s*"([^"]+)"', + re.MULTILINE, + ) + + GIN_HANDLE_PATTERN = re.compile( + r'(?:routers?|router)\s*\.\s*Handle\s*\("(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)"\s*,\s*"([^"]+)"', + re.MULTILINE, + ) + + GIN_SIMPLE_PATTERN = re.compile( + r'r\s*\.\s*(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\s*\(\s*"([^"]+)"', + re.MULTILINE, + ) + + CHI_PATTERN = re.compile( + r'(?:routers?|router|Mux|r)\s*\.\s*(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\s*\(\s*"([^"]+)"', + re.MULTILINE, + ) + + CHI_MOUNT_PATTERN = re.compile( + r'Mount\s*\(\s*"([^"]+)"\s*,\s*[a-zA-Z]+\)', + re.MULTILINE, + ) + + HTTP_METHODS = {"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"} + + def detect_endpoints(self, file_path: Path) -> list[Endpoint]: + """Detect endpoints in a Go file.""" + content = file_path.read_text() + endpoints = [] + + framework = self._detect_framework(content) + + if framework == "chi": + endpoints.extend(self._detect_chi(content, file_path)) + else: + endpoints.extend(self._detect_gin(content, file_path)) + + return endpoints + + def _detect_framework(self, content: str) -> Optional[str]: + """Auto-detect the Go framework.""" + if 'github.com/go-chi/chi' in content or '"github.com/go-chi/chi' in content: + return "chi" + if 'github.com/gin-gonic/gin' in content or '"github.com/gin-gonic/gin' in content: + return "gin" + return None + + def _detect_gin(self, content: str, file_path: Path) -> list[Endpoint]: + """Detect Gin endpoints.""" + endpoints = [] + + for match in self.GIN_SIMPLE_PATTERN.finditer(content): + method, path = match.groups() + endpoint = Endpoint( + path=path, + method=HTTPMethod(method), + summary=f"{method} {path}", + file_path=str(file_path), + line_number=content[:match.start()].count("\n") + 1, + ) + endpoints.append(endpoint) + + for match in self.GIN_PATTERN.finditer(content): + method, path = match.groups() + endpoint = Endpoint( + path=path, + method=HTTPMethod(method), + summary=f"{method} {path}", + file_path=str(file_path), + line_number=content[:match.start()].count("\n") + 1, + ) + endpoints.append(endpoint) + + for match in self.GIN_HANDLE_PATTERN.finditer(content): + method, path = match.groups() + endpoint = Endpoint( + path=path, + method=HTTPMethod(method), + summary=f"{method} {path}", + file_path=str(file_path), + line_number=content[:match.start()].count("\n") + 1, + ) + endpoints.append(endpoint) + + return endpoints + + def _detect_chi(self, content: str, file_path: Path) -> list[Endpoint]: + """Detect chi endpoints.""" + endpoints = [] + + for match in self.CHI_PATTERN.finditer(content): + method, path = match.groups() + endpoint = Endpoint( + path=path, + method=HTTPMethod(method), + summary=f"{method} {path}", + file_path=str(file_path), + line_number=content[:match.start()].count("\n") + 1, + ) + endpoints.append(endpoint) + + return endpoints