Files

398 lines
8.7 KiB
Python

"""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()