"""Project type detection based on existing files.""" import os from pathlib import Path from typing import Optional DETECTION_RULES = { 'node': [ 'package.json', 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'node_modules', ], 'python': [ 'requirements.txt', 'setup.py', 'setup.cfg', 'pyproject.toml', 'Pipfile', 'venv', '.venv', ], 'django': [ 'manage.py', 'django-admin.py', 'settings.py', 'urls.py', 'wsgi.py', ], 'flask': [ 'app.py', 'wsgi.py', 'requirements.txt', ], 'rails': [ 'Gemfile', 'Rakefile', 'config.ru', 'app/controllers', 'config/routes.rb', ], 'react': [ 'package.json', 'react-app', 'src/App.js', 'src/App.tsx', ], 'vue': [ 'package.json', 'vue.config.js', 'src/main.js', ], 'angular': [ 'angular.json', 'package.json', 'src/main.ts', ], 'dotnet': [ '*.csproj', '*.sln', 'Program.cs', 'Startup.cs', ], 'java': [ 'pom.xml', 'build.gradle', 'src/main/java', 'settings.gradle', ], 'go': [ 'go.mod', 'go.sum', 'main.go', ], 'rust': [ 'Cargo.toml', 'Cargo.lock', 'src/main.rs', ], 'php': [ 'composer.json', 'index.php', 'wp-config.php', ], 'laravel': [ 'artisan', 'composer.json', 'bootstrap/app.php', ], 'maven': [ 'pom.xml', 'src/main/java', ], 'gradle': [ 'build.gradle', 'build.gradle.kts', 'settings.gradle', 'settings.gradle.kts', ], 'wordpress': [ 'wp-config.php', 'wp-content', 'wp-admin', ], 'cocoapods': [ 'Podfile', 'Pods', '*.xcworkspace', ], 'swift': [ 'Package.swift', '*.xcodeproj', '*.xcworkspace', ], 'kotlin': [ 'build.gradle.kts', 'settings.gradle.kts', 'src/main/kotlin', ], 'scala': [ 'build.sbt', 'project', 'src/main/scala', ], 'elixir': [ 'mix.exs', 'config/config.exs', ], 'clojure': [ 'project.clj', 'deps.edn', ], 'ruby': [ 'Gemfile', 'Rakefile', 'config.ru', ], 'aws': [ '.aws', 'aws-cdk.json', ], 'terraform': [ '*.tf', '*.tfstate', 'terraform.tfstate', ], 'docker': [ 'Dockerfile', 'docker-compose.yml', 'docker-compose.yaml', ], 'kubernetes': [ 'kustomization.yaml', '*.yaml', '*.yml', 'helm', ], 'unity': [ 'Assets', 'ProjectSettings', 'Packages', ], 'unreal': [ '*.uproject', 'Engine', 'Source', ], 'Qt': [ '*.pro', '*.pri', 'CMakeLists.txt', ], 'opencv': [ 'CMakeLists.txt', 'cvconfig.h', ], 'tensorflow': [ 'saved_model.pb', '.tf', 'tensorflow', ], 'jupyter': [ '*.ipynb', '.ipynb_checkpoints', ], 'vim': [ '.vimrc', '.vim', ], 'emacs': [ '.emacs', '.emacs.d', ], 'linux': [ '.config', ], 'macos': [ '.DS_Store', ], 'windows': [ 'Thumbs.db', 'Desktop.ini', ], 'visualstudiocode': [ '.vscode', ], 'jetbrains': [ '.idea', '*.iml', ], 'eclipse': [ '.project', '.classpath', ], 'netlify': [ 'netlify.toml', 'netlify.toml', ], 'vercel': [ '.vercel', ], 'firebase': [ '.firebaserc', 'firebase.json', ], 'heroku': [ 'Procfile', 'app.json', ], 'sublimetext': [ '*.sublime-workspace', '*.sublime-project', ], } class ProjectDetector: """Detects project type based on existing files.""" def __init__(self, path: Optional[Path] = None): """Initialize detector. Args: path: Path to scan (defaults to current directory) """ self.path = path or Path.cwd() def scan_directory(self, max_depth: int = 3) -> dict[str, list[str]]: """Scan directory and find detection markers. Args: max_depth: Maximum directory depth to scan Returns: Dictionary mapping technologies to found files """ found = {} for tech, markers in DETECTION_RULES.items(): matched = [] for marker in markers: if '*' in marker: matched.extend(self._find_pattern(marker, max_depth)) else: marker_path = self.path / marker if marker_path.exists(): matched.append(marker) if matched: found[tech] = matched return found def _find_pattern(self, pattern: str, max_depth: int) -> list[str]: """Find files matching a glob pattern. Args: pattern: Glob pattern to match max_depth: Maximum directory depth Returns: List of matching file paths """ import glob matches = [] for depth in range(1, max_depth + 1): search_pattern = '/'.join(['*'] * depth) + f'/{pattern.replace("*", "*")}' full_pattern = str(self.path / search_pattern) found = glob.glob(full_pattern, recursive=True) matches.extend([os.path.basename(f) for f in found]) return list(set(matches)) def detect(self, min_confidence: int = 1) -> list[str]: """Detect project technologies. Args: min_confidence: Minimum number of markers to match Returns: List of detected technology names """ found = self.scan_directory() detected = [ tech for tech, markers in found.items() if len(markers) >= min_confidence ] return sorted(detected) def get_detection_details(self) -> list[dict]: """Get detailed detection results. Returns: List of dictionaries with detection details """ found = self.scan_directory() results = [ { 'technology': tech, 'matched_files': markers, 'confidence': len(markers) } for tech, markers in found.items() ] return sorted(results, key=lambda x: x['confidence'], reverse=True) def suggest_gitignore(self) -> list[str]: """Suggest technologies for .gitignore generation. Returns: List of suggested technology names """ detected = self.detect() suggestions = [] if 'node' in detected: suggestions.append('node') if 'python' in detected: suggestions.append('python') if 'django' in detected: suggestions.append('django') if 'rails' in detected: suggestions.append('rails') if 'java' in detected: suggestions.append('java') if 'go' in detected: suggestions.append('go') if 'rust' in detected: suggestions.append('rust') if 'dotnet' in detected: suggestions.append('dotnet') if 'docker' in detected: suggestions.append('docker') if 'macos' in detected: suggestions.append('macos') if 'windows' in detected: suggestions.append('windows') if 'linux' in detected: suggestions.append('linux') if 'jetbrains' in detected: suggestions.append('jetbrains') if 'visualstudiocode' in detected: suggestions.append('visualstudiocode') return list(set(suggestions)) def detect_project(path: Optional[Path] = None) -> list[str]: """Convenience function to detect project type. Args: path: Path to scan (defaults to current directory) Returns: List of detected technology names """ detector = ProjectDetector(path) return detector.detect() def suggest_gitignore(path: Optional[Path] = None) -> list[str]: """Convenience function to suggest gitignore technologies. Args: path: Path to scan (defaults to current directory) Returns: List of suggested technology names """ detector = ProjectDetector(path) return detector.suggest_gitignore()