initial
This commit is contained in:
23 files changed
+2117
No files matched your search
@@ -0,0 +1,13 @@
|
||||
"""
|
||||
Gitignore generator module.
|
||||
"""
|
||||
|
||||
from .cli import cli
|
||||
from .generator import GitignoreGenerator
|
||||
from .detector import ProjectDetector
|
||||
|
||||
__all__ = [
|
||||
"cli",
|
||||
"GitignoreGenerator",
|
||||
"ProjectDetector"
|
||||
]
|
||||
@@ -0,0 +1,84 @@
|
||||
"""
|
||||
Gitignore generation CLI.
|
||||
"""
|
||||
import os
|
||||
import click
|
||||
from rich.console import Console
|
||||
from rich.prompt import Confirm
|
||||
from rich.progress import Progress, SpinnerColumn, TextColumn
|
||||
from .generator import GitignoreGenerator
|
||||
from .detector import ProjectDetector
|
||||
|
||||
console = Console()
|
||||
|
||||
@click.group()
|
||||
def cli():
|
||||
"""Generate .gitignore files."""
|
||||
pass
|
||||
|
||||
@cli.command()
|
||||
@click.option("--directory", "-d", default=".", help="Directory to scan")
|
||||
@click.option("--output", "-o", default=".gitignore", help="Output file path")
|
||||
def auto(directory: str, output: str):
|
||||
"""Automatically detect project types and generate .gitignore."""
|
||||
with Progress(
|
||||
SpinnerColumn(),
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
console=console
|
||||
) as progress:
|
||||
# Initialize services
|
||||
progress.add_task("Initializing...", total=None)
|
||||
detector = ProjectDetector(directory)
|
||||
generator = GitignoreGenerator()
|
||||
|
||||
# Detect project types
|
||||
progress.add_task("Detecting project types...", total=None)
|
||||
detected_types = detector.detect_project_types()
|
||||
|
||||
if not detected_types:
|
||||
console.print("[yellow]No project types detected. Using generic .gitignore.")
|
||||
else:
|
||||
console.print(f"\n[green]Detected project types:[/] {', '.join(detected_types)}")
|
||||
|
||||
# Check if output file exists
|
||||
if os.path.exists(output):
|
||||
if not Confirm.ask(f"\n[yellow]File {output} already exists. Overwrite?"):
|
||||
return
|
||||
|
||||
# Generate .gitignore
|
||||
progress.add_task("Generating .gitignore...", total=None)
|
||||
generator.update_gitignore_file(detected_types, output)
|
||||
|
||||
console.print(f"\n[green]Generated .gitignore at {output}")
|
||||
|
||||
@cli.command()
|
||||
@click.argument("types", nargs=-1, required=True)
|
||||
@click.option("--output", "-o", default=".gitignore", help="Output file path")
|
||||
def generate(types: tuple, output: str):
|
||||
"""Generate .gitignore for specified types."""
|
||||
with Progress(
|
||||
SpinnerColumn(),
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
console=console
|
||||
) as progress:
|
||||
# Initialize services
|
||||
progress.add_task("Initializing...", total=None)
|
||||
generator = GitignoreGenerator()
|
||||
|
||||
# Check if output file exists
|
||||
if os.path.exists(output):
|
||||
if not Confirm.ask(f"\n[yellow]File {output} already exists. Overwrite?"):
|
||||
return
|
||||
|
||||
# Generate .gitignore
|
||||
progress.add_task("Generating .gitignore...", total=None)
|
||||
generator.update_gitignore_file(list(types), output)
|
||||
|
||||
console.print(f"\n[green]Generated .gitignore at {output}")
|
||||
|
||||
# Add command aliases
|
||||
cli.add_command(auto, name="a")
|
||||
cli.add_command(generate, name="g")
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
@@ -0,0 +1,161 @@
|
||||
"""
|
||||
Project type detection for gitignore generation.
|
||||
"""
|
||||
import os
|
||||
from typing import List, Dict, Set
|
||||
|
||||
class ProjectDetector:
|
||||
"""Detect project types based on file patterns."""
|
||||
|
||||
# File patterns for different project types
|
||||
PROJECT_PATTERNS = {
|
||||
# Languages
|
||||
"python": ["*.py", "requirements.txt", "setup.py", "Pipfile", "pyproject.toml"],
|
||||
"javascript": ["package.json", "*.js", "*.jsx", "*.ts", "*.tsx"],
|
||||
"typescript": ["tsconfig.json", "*.ts", "*.tsx"],
|
||||
"java": ["*.java", "pom.xml", "build.gradle", "*.class"],
|
||||
"kotlin": ["*.kt", "*.kts"],
|
||||
"go": ["go.mod", "go.sum", "*.go"],
|
||||
"rust": ["Cargo.toml", "Cargo.lock", "*.rs"],
|
||||
"ruby": ["Gemfile", "*.rb", "*.gemspec"],
|
||||
"php": ["composer.json", "*.php"],
|
||||
"csharp": ["*.cs", "*.csproj", "*.sln"],
|
||||
"swift": ["*.swift", "*.xcodeproj"],
|
||||
"objective-c": ["*.m", "*.h", "*.mm"],
|
||||
|
||||
# Frameworks
|
||||
"django": ["manage.py", "wsgi.py", "asgi.py"],
|
||||
"flask": ["app.py", "flask_app.py"],
|
||||
"fastapi": ["main.py", "app.py"],
|
||||
"react": ["src/App.js", "src/App.tsx"],
|
||||
"vue": ["vue.config.js", "*.vue"],
|
||||
"angular": ["angular.json", "tsconfig.json"],
|
||||
"spring": ["application.properties", "application.yml"],
|
||||
"laravel": ["artisan", "composer.json"],
|
||||
"rails": ["config/routes.rb", "Gemfile"],
|
||||
"express": ["app.js", "server.js"],
|
||||
"next": ["next.config.js"],
|
||||
"nuxt": ["nuxt.config.js"],
|
||||
|
||||
# Build Tools
|
||||
"maven": ["pom.xml"],
|
||||
"gradle": ["build.gradle", "settings.gradle"],
|
||||
"npm": ["package.json"],
|
||||
"yarn": ["yarn.lock"],
|
||||
"pip": ["requirements.txt"],
|
||||
"poetry": ["pyproject.toml"],
|
||||
"cargo": ["Cargo.toml"],
|
||||
"composer": ["composer.json"],
|
||||
|
||||
# IDEs and Editors
|
||||
"vscode": [".vscode/settings.json"],
|
||||
"intellij": [".idea/workspace.xml"],
|
||||
"eclipse": [".project", ".classpath"],
|
||||
"vim": [".vimrc", ".vim/"],
|
||||
"emacs": [".emacs", ".emacs.d/"],
|
||||
"sublime": ["*.sublime-project", "*.sublime-workspace"],
|
||||
"atom": [".atom/"],
|
||||
|
||||
# Operating Systems
|
||||
"windows": ["*.exe", "*.dll", "*.sys"],
|
||||
"macos": [".DS_Store", "*.app"],
|
||||
"linux": ["*.so", "*.o"],
|
||||
|
||||
# Testing Frameworks
|
||||
"jest": ["jest.config.js"],
|
||||
"pytest": ["pytest.ini", "conftest.py"],
|
||||
"junit": ["junit.xml"],
|
||||
"mocha": ["mocha.opts"],
|
||||
|
||||
# Documentation
|
||||
"sphinx": ["docs/conf.py"],
|
||||
"mkdocs": ["mkdocs.yml"],
|
||||
"docusaurus": ["docusaurus.config.js"],
|
||||
|
||||
# Databases
|
||||
"postgresql": ["*.sql", "*.dump"],
|
||||
"mysql": ["*.sql", "*.dump"],
|
||||
"mongodb": ["*.bson"],
|
||||
"sqlite": ["*.db", "*.sqlite"],
|
||||
|
||||
# Cloud Platforms
|
||||
"aws": [".aws/", "aws.json"],
|
||||
"azure": ["azure.yaml", "azure.json"],
|
||||
"gcp": ["gcloud.json"],
|
||||
|
||||
# Containerization
|
||||
"docker": ["Dockerfile", "docker-compose.yml"],
|
||||
"kubernetes": ["k8s/", "*.yaml"],
|
||||
|
||||
# CI/CD
|
||||
"github-actions": [".github/workflows/"],
|
||||
"gitlab-ci": [".gitlab-ci.yml"],
|
||||
"jenkins": ["Jenkinsfile"],
|
||||
"travis": [".travis.yml"],
|
||||
"circleci": [".circleci/config.yml"],
|
||||
|
||||
# Static Site Generators
|
||||
"jekyll": ["_config.yml"],
|
||||
"hugo": ["config.toml", "config.yaml"],
|
||||
"gatsby": ["gatsby-config.js"],
|
||||
|
||||
# Game Development
|
||||
"unity": ["*.unity", "*.unityproj"],
|
||||
"unreal": ["*.uproject"],
|
||||
|
||||
# Machine Learning
|
||||
"tensorflow": ["*.pb", "*.h5"],
|
||||
"pytorch": ["*.pt", "*.pth"],
|
||||
"scikit-learn": ["*.joblib"]
|
||||
}
|
||||
|
||||
def __init__(self, directory: str = "."):
|
||||
"""Initialize project detector.
|
||||
|
||||
Args:
|
||||
directory: Directory to scan
|
||||
"""
|
||||
self.directory = directory
|
||||
|
||||
def detect_project_types(self) -> List[str]:
|
||||
"""Detect project types in the directory.
|
||||
|
||||
Returns:
|
||||
List of detected project types
|
||||
"""
|
||||
detected_types = set()
|
||||
|
||||
# Scan directory for files
|
||||
for root, _, files in os.walk(self.directory):
|
||||
for file in files:
|
||||
file_path = os.path.join(root, file)
|
||||
rel_path = os.path.relpath(file_path, self.directory)
|
||||
|
||||
# Check each project type's patterns
|
||||
for type_, patterns in self.PROJECT_PATTERNS.items():
|
||||
if any(self._matches_pattern(rel_path, pattern) for pattern in patterns):
|
||||
detected_types.add(type_)
|
||||
|
||||
return sorted(list(detected_types))
|
||||
|
||||
def _matches_pattern(self, path: str, pattern: str) -> bool:
|
||||
"""Check if a path matches a pattern.
|
||||
|
||||
Args:
|
||||
path: Path to check
|
||||
pattern: Pattern to match
|
||||
|
||||
Returns:
|
||||
True if path matches pattern
|
||||
"""
|
||||
# Handle directory patterns
|
||||
if pattern.endswith("/"):
|
||||
return path.startswith(pattern[:-1])
|
||||
|
||||
# Handle wildcard patterns
|
||||
if "*" in pattern:
|
||||
import fnmatch
|
||||
return fnmatch.fnmatch(path, pattern)
|
||||
|
||||
# Handle exact matches
|
||||
return path == pattern
|
||||
@@ -0,0 +1,106 @@
|
||||
"""
|
||||
Gitignore generator using gitignore.io API.
|
||||
"""
|
||||
import os
|
||||
import requests
|
||||
from typing import List, Dict
|
||||
|
||||
class GitignoreGenerator:
|
||||
"""Gitignore generator using gitignore.io API."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize gitignore generator."""
|
||||
self.api_url = "https://www.toptal.com/developers/gitignore/api"
|
||||
|
||||
def generate_gitignore(self, types: List[str]) -> str:
|
||||
"""Generate .gitignore content for specified types.
|
||||
|
||||
Args:
|
||||
types: List of project types
|
||||
|
||||
Returns:
|
||||
Generated .gitignore content
|
||||
"""
|
||||
if not types:
|
||||
return self._get_generic_gitignore()
|
||||
|
||||
try:
|
||||
# Get gitignore content from API
|
||||
response = requests.get(f"{self.api_url}/{','.join(types)}")
|
||||
response.raise_for_status()
|
||||
|
||||
# Add header
|
||||
header = "# Generated by devtools gitignore generator\n"
|
||||
header += f"# Types: {', '.join(types)}\n\n"
|
||||
|
||||
return header + response.text
|
||||
|
||||
except requests.RequestException:
|
||||
# Fallback to generic gitignore if API fails
|
||||
return self._get_generic_gitignore()
|
||||
|
||||
def _get_generic_gitignore(self) -> str:
|
||||
"""Get a generic .gitignore template.
|
||||
|
||||
Returns:
|
||||
Generic .gitignore content
|
||||
"""
|
||||
return """# Generic .gitignore
|
||||
# Generated by devtools gitignore generator
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# IDE files
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Build directories
|
||||
build/
|
||||
dist/
|
||||
out/
|
||||
target/
|
||||
|
||||
# Dependencies
|
||||
node_modules/
|
||||
venv/
|
||||
env/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
*.bak
|
||||
"""
|
||||
|
||||
def update_gitignore_file(self, types: List[str], output_file: str) -> None:
|
||||
"""Generate and update a .gitignore file.
|
||||
|
||||
Args:
|
||||
types: List of project types
|
||||
output_file: Path to the .gitignore file
|
||||
"""
|
||||
content = self.generate_gitignore(types)
|
||||
|
||||
# Write to file
|
||||
with open(output_file, "w") as f:
|
||||
f.write(content)
|
||||
Reference in new issue
Block a user