"""Project type detection from directory structure.""" import os from pathlib import Path from typing import Dict, List, Optional, Set class ProjectDetector: """Detect project type from directory structure and files.""" PATTERNS: Dict[str, List[str]] = { "python": [ "requirements.txt", "setup.py", "setup.cfg", "pyproject.toml", "Pipfile", "pyvenv.cfg", "__pycache__", ], "javascript": [ "package.json", "package-lock.json", "yarn.lock", "npm-debug.log", "node_modules", ".npmrc", ], "typescript": [ "tsconfig.json", "tsconfig.app.json", "tsconfig.spec.json", "package.json", ], "java": [ "pom.xml", "build.gradle", "settings.gradle", "gradlew", "mvnw", ".mvn", ], "go": ["go.mod", "go.sum", "Gopkg.toml", "Gopkg.lock", "main.go"], "rust": ["Cargo.toml", "Cargo.lock", "rust-toolchain", "src/main.rs"], "dotnet": [ "*.csproj", "*.fsproj", "*.vbproj", "*.sln", "*.cshtml", ], "php": [ "composer.json", "composer.lock", "wp-config.php", "vendor", "index.php", ], "ruby": ["Gemfile", "Gemfile.lock", "Rakefile", "config.ru"], "django": ["manage.py", "django-admin.py", "settings.py", "wsgi.py"], "flask": ["app.py", "wsgi.py", "application.py"], "react": ["package.json", "react.config.js", "vite.config.ts"], "vue": ["package.json", "vue.config.js", "vite.config.ts"], "angular": [ "angular.json", "tsconfig.json", "karma.conf.js", "package.json", ], "rails": [ "config.ru", "Gemfile", "app/controllers", "app/models", "app/views", ], "laravel": [ "artisan", "composer.json", "bootstrap/app.php", "config/app.php", ], "spring": [ "pom.xml", "build.gradle", "src/main/java", "src/main/resources", ], "vscode": [".vscode", ".vscode/settings.json"], "jetbrains": [ ".idea", "*.iml", "*.ipr", "*.iws", ".idea_modules", ], "visualstudiocode": [ ".vscode", ".vscode/extensions.json", ".vscode/settings.json", ], "linux": [".linux", "linux"], "macos": [".DS_Store", "macos"], "windows": ["Thumbs.db", "desktop.ini", "windows"], "docker": [ "Dockerfile", "docker-compose.yml", "docker-compose.yaml", ".docker", ], "gradle": [ "build.gradle", "build.gradle.kts", "gradle.properties", "gradlew", "gradlew.bat", ], "maven": ["pom.xml", ".mvn", "mvnw", "mvnw.cmd"], "jupyter": [ "*.ipynb", ".ipynb_checkpoints", "jupyter_notebook_config.py", ], "terraform": ["*.tf", "*.tfvars", "terraform.tfstate", ".terraform"], } FRAMEWORK_INDICATORS: Dict[str, List[str]] = { "django": ["Django", "django"], "flask": ["Flask", "flask"], "react": ["react", "react-dom", "create-react-app"], "vue": ["vue", "@vue"], "angular": ["@angular/core", "angular"], "rails": ["Rails", "rails"], "laravel": ["Laravel", "laravel/framework"], "spring": ["org.springframework", "spring"], } def __init__(self, path: Path) -> None: """Initialize detector with path to scan.""" self.path = path self.detected_types: Set[str] = set() def _file_exists_in_tree( self, filename: str, path: Optional[Path] = None ) -> bool: """Check if file exists in directory tree.""" search_path = path or self.path if search_path.is_file(): return search_path.name == filename for root, dirs, files in os.walk(search_path): if filename in files: return True for d in dirs[:]: if d == "__pycache__" or d == "node_modules": dirs.remove(d) return False def _file_exists_in_root(self, filename: str) -> bool: """Check if file exists in root directory.""" return (self.path / filename).exists() def _scan_package_json(self) -> Optional[Set[str]]: """Scan package.json for framework indicators.""" package_json = self.path / "package.json" if package_json.exists(): try: import json with open(package_json) as f: data = json.load(f) deps = set() if "dependencies" in data: deps.update(data["dependencies"].keys()) if "devDependencies" in data: deps.update(data["devDependencies"].keys()) for framework, indicators in self.FRAMEWORK_INDICATORS.items(): if framework in ["react", "vue", "angular"]: for indicator in indicators: if indicator in deps: self.detected_types.add(framework) except (json.JSONDecodeError, OSError): pass return None def _scan_requirements_txt(self) -> None: """Scan requirements.txt for framework indicators.""" req_file = self.path / "requirements.txt" if req_file.exists(): try: with open(req_file) as f: content = f.read().lower() for framework, indicators in self.FRAMEWORK_INDICATORS.items(): if framework in ["django", "flask"]: for indicator in indicators: if indicator.lower() in content: self.detected_types.add(framework) break except OSError: pass def _scan_build_files(self) -> None: """Scan build files for framework indicators.""" for root, dirs, files in os.walk(self.path): dirs[:] = [ d for d in dirs if d not in ["__pycache__", "node_modules", ".git"] ] for filename in files: if filename in ["pom.xml", "build.gradle"]: filepath = Path(root) / filename try: with open(filepath) as f: content = f.read() for framework, indicators in self.FRAMEWORK_INDICATORS.items(): if framework == "spring": for indicator in indicators: if indicator in content: self.detected_types.add(framework) break except OSError: pass def detect(self) -> List[str]: """Detect project types from directory structure.""" self.detected_types = set() for project_type, patterns in self.PATTERNS.items(): for pattern in patterns: if "*" in pattern: import fnmatch for root, dirs, files in os.walk(self.path): dirs[:] = [ d for d in dirs if d not in [ "__pycache__", "node_modules", ".git", ".idea", ".vscode", ] ] for filename in files: if fnmatch.fnmatch(filename, pattern): if project_type in [ "dotnet", "jetbrains", ]: self.detected_types.add(project_type) break elif "/" not in pattern: if self._file_exists_in_root(pattern): if project_type in [ "python", "javascript", "typescript", "java", "go", "rust", "dotnet", "php", "ruby", "vscode", "jetbrains", "visualstudiocode", "linux", "macos", "windows", "docker", "gradle", "maven", "jupyter", "terraform", ]: self.detected_types.add(project_type) break self._scan_package_json() self._scan_requirements_txt() self._scan_build_files() if "javascript" in self.detected_types and "typescript" in self.detected_types: self.detected_types.discard("javascript") return sorted(self.detected_types) def get_language(self) -> Optional[str]: """Get primary programming language.""" languages = [ "python", "javascript", "typescript", "java", "go", "rust", "dotnet", "php", "ruby", ] for lang in languages: if lang in self.detected_types: return lang return None def get_framework(self) -> Optional[str]: """Get framework if detected.""" frameworks = [ "django", "flask", "react", "vue", "angular", "rails", "laravel", "spring", ] for fw in frameworks: if fw in self.detected_types: return fw return None def get_ide(self) -> Optional[str]: """Get IDE if detected.""" ides = ["vscode", "jetbrains", "visualstudiocode"] for ide in ides: if ide in self.detected_types: return ide return None