Initial commit: gitignore-generator v0.1.0
Some checks failed
CI / test (push) Has been cancelled

This commit is contained in:
2026-02-01 03:31:33 +00:00
parent 5cc18db1c8
commit 8924aca7c4

331
src/detector.py Normal file
View File

@@ -0,0 +1,331 @@
"""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