From 8924aca7c4ae41f56e1a71dde5ba5fcb222a7897 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Sun, 1 Feb 2026 03:31:33 +0000 Subject: [PATCH] Initial commit: gitignore-generator v0.1.0 --- src/detector.py | 331 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 src/detector.py diff --git a/src/detector.py b/src/detector.py new file mode 100644 index 0000000..1336f5b --- /dev/null +++ b/src/detector.py @@ -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