Initial upload: gitignore-generator-cli v1.0.0 with CI/CD workflow
This commit is contained in:
397
gitignore_generator/detector.py
Normal file
397
gitignore_generator/detector.py
Normal file
@@ -0,0 +1,397 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user