diff --git a/config_auditor/discovery.py b/config_auditor/discovery.py new file mode 100644 index 0000000..aaabecb --- /dev/null +++ b/config_auditor/discovery.py @@ -0,0 +1,119 @@ +from dataclasses import dataclass +from pathlib import Path +from typing import List, Optional + + +@dataclass +class ConfigFile: + path: Path + format: str + name: str + + +class ConfigDiscovery: + PATTERNS = { + "json": [ + "package.json", + "tsconfig*.json", + ".eslintrc*.json", + ".prettierrc*.json", + ".babelrc*.json", + "webpack.config.js", + "rollup.config.js", + "nest-cli.json", + "angular.json", + "pyproject.toml", + "poetry.toml", + "mypy.ini", + "pytest.ini", + "setup.cfg", + ".coveragerc", + "tox.ini", + ], + "yaml": [ + ".gitlab-ci.yml", + ".github/**/*.yml", + ".github/**/*.yaml", + "docker-compose.yml", + "*.k8s.yaml", + "*.k8s.yml", + ".ansible.yml", + ".ansible/**/*.yml", + ], + "toml": [ + "pyproject.toml", + "poetry.toml", + "Cargo.toml", + "PDM.toml", + ], + } + + def __init__(self, max_depth: int = 3): + self.max_depth = max_depth + + def discover(self, path: Path) -> List[ConfigFile]: + config_files = [] + + for format_patterns in self.PATTERNS.values(): + for pattern in format_patterns: + matches = self._find_matches(path, pattern) + for match in matches: + format_type = self._detect_format(match) + if format_type: + config_files.append(ConfigFile( + path=match, + format=format_type, + name=match.name + )) + + return config_files + + def _find_matches(self, base_path: Path, pattern: str) -> List[Path]: + matches = [] + + if "**" in pattern: + parts = pattern.split("**") + if len(parts) > 1: + search_pattern = parts[-1].lstrip("/") + else: + search_pattern = "*" + for p in base_path.rglob(search_pattern): + if p.is_file() and self._matches_pattern(p.name, search_pattern): + matches.append(p) + elif "*" in pattern: + for p in base_path.rglob(pattern): + if p.is_file(): + matches.append(p) + else: + full_path = base_path / pattern + if full_path.exists(): + matches.append(full_path) + + return list(set(matches)) + + def _matches_pattern(self, filename: str, pattern: str) -> bool: + from fnmatch import fnmatch + return fnmatch(filename, pattern) + + def _detect_format(self, path: Path) -> Optional[str]: + suffix = path.suffix.lower() + name = path.name.lower() + + if suffix == ".json" or "eslintrc" in name or "babelrc" in name: + return "json" + elif suffix in (".yaml", ".yml"): + return "yaml" + elif suffix == ".toml" or name == "pyproject.toml": + return "toml" + + if path.exists(): + try: + content = path.read_text() + if content.strip().startswith("{") or content.strip().startswith("["): + return "json" + elif content.strip().startswith("---") or ":" in content.split("\n")[0]: + return "yaml" + except Exception: + pass + + return None