diff --git a/src/auto_readme/parsers/go_parser.py b/src/auto_readme/parsers/go_parser.py new file mode 100644 index 0000000..7261990 --- /dev/null +++ b/src/auto_readme/parsers/go_parser.py @@ -0,0 +1,92 @@ +"""Go dependency parser for go.mod.""" + +import re +from pathlib import Path +from typing import Optional + +from . import BaseParser, Dependency + + +class GoDependencyParser(BaseParser): + """Parser for Go go.mod files.""" + + def can_parse(self, path: Path) -> bool: + """Check if the file is a go.mod.""" + return path.name.lower() == "go.mod" + + def parse(self, path: Path) -> list[Dependency]: + """Parse dependencies from go.mod.""" + if not path.exists(): + return [] + + dependencies = [] + try: + content = path.read_text(encoding="utf-8") + lines = content.splitlines() + except (OSError, UnicodeDecodeError): + return dependencies + + in_require_block = False + current_block_indent = 0 + + for line in lines: + stripped = line.strip() + + if stripped.startswith("require ("): + in_require_block = True + current_block_indent = len(line) - len(line.lstrip()) + continue + + if stripped.startswith(")"): + in_require_block = False + continue + + if in_require_block or stripped.startswith("require "): + if not in_require_block: + if "require (" in line: + continue + match = self._parse_single_require(line) + if match: + dependencies.append(match) + else: + current_indent = len(line) - len(line.lstrip()) + if current_indent < current_block_indent: + in_require_block = False + continue + + if stripped and not stripped.startswith("//"): + match = self._parse_require_block_line(line) + if match: + dependencies.append(match) + + return dependencies + + def _parse_single_require(self, line: str) -> Optional[Dependency]: + """Parse a single require statement.""" + match = re.match(r"require\s+([^\s]+)\s+(.+)", line) + if match: + name = match.group(1) + version = match.group(2).strip() + return Dependency( + name=name, + version=self._normalize_version(version), + is_dev=False, + ) + return None + + def _parse_require_block_line(self, line: str) -> Optional[Dependency]: + """Parse a line within a require block.""" + stripped = line.strip() + if not stripped or stripped.startswith("//"): + return None + + match = re.match(r"([^\s]+)\s+v?(.+)", stripped) + if match: + name = match.group(1) + version = match.group(2).strip() + return Dependency( + name=name, + version=self._normalize_version(version), + is_dev=False, + ) + return None