From e5f63d482a1065a7f1b201e24e0a70d560f7e2cd Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Fri, 30 Jan 2026 15:34:04 +0000 Subject: [PATCH] Initial upload: gitignore-generator-cli v1.0.0 with CI/CD workflow --- gitignore_generator/detector.py | 397 ++++++++++++++++++++++++++++++++ 1 file changed, 397 insertions(+) create mode 100644 gitignore_generator/detector.py diff --git a/gitignore_generator/detector.py b/gitignore_generator/detector.py new file mode 100644 index 0000000..edb0958 --- /dev/null +++ b/gitignore_generator/detector.py @@ -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()