120 lines
3.5 KiB
Python
120 lines
3.5 KiB
Python
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
|