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