"""Gitignore generator for Project Scaffold CLI.""" from pathlib import Path from typing import Optional, Set class GitignoreGenerator: """Generate language-specific .gitignore files.""" SUPPORTED_LANGUAGES = ["python", "nodejs", "go", "rust"] GITIGNORE_TEMPLATES = { "python": """__pycache__/ *.py[cod] *$py.class *.so .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg venv/ ENV/ env/ .venv/ .pytest_cache/ .coverage .coverage.* htmlcov/ .tox/ .nox/ *.manifest *.spec """, "nodejs": """node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* *.log .DS_Store dist/ build/ .nyc_output/ coverage/ *.suo *.ntvs* *.njsproj *.sln *.sw? """, "go": """# Binaries *.exe *.exe~ *.dll *.so *.dylib # Test binary *.test # Output of go coverage *.out # Go workspace go.work # Vendor directory vendor/ # IDE .idea/ .vscode/ *.swp *.swo """, "rust": """# Cargo target/ Cargo.lock # IDE .idea/ .vscode/ *.swp *.swo # Backup files *~ #*# #* .backup # Build artifacts *.rlib *.dylib *.dll # IDE .vscode/ .idea/ """, } ADDITIONAL_PATTERNS = { "python": """*.pyc *.pyo .env .env.local .vscode/ .idea/ """, "nodejs": """package-lock.json yarn.lock .env .env.local """, "go": """*.out *.test coverage.txt """, "rust": """**/*.rs.bk Cargo.lock """, } def __init__(self): self.templates_dir = self._get_templates_dir() def _get_templates_dir(self) -> Path: """Get the directory containing gitignore templates.""" return Path(__file__).parent / "templates" / "gitignore" def generate( self, language: str, output_path: Path, extra_patterns: Optional[Set[str]] = None ) -> None: """Generate a .gitignore file for the specified language.""" if language not in self.SUPPORTED_LANGUAGES: raise ValueError( f"Unsupported language: {language}. " f"Supported: {', '.join(self.SUPPORTED_LANGUAGES)}" ) content = self.GITIGNORE_TEMPLATES.get(language, "") extra = self.ADDITIONAL_PATTERNS.get(language, "") if extra: content += extra if extra_patterns: content += "\n".join(sorted(extra_patterns)) + "\n" content += "\n# Editor directories\n.idea/\n.vscode/\n*.swp\n*.swo\n*~\n" output_path.write_text(content) def generate_from_template(self, template_name: str, output_path: Path) -> None: """Generate .gitignore from a template file.""" template_path = self.templates_dir / template_name if not template_path.exists(): raise FileNotFoundError( f"Gitignore template not found: {template_path}" ) content = template_path.read_text() output_path.write_text(content) def list_available_templates(self) -> list[str]: """List available gitignore templates.""" if not self.templates_dir.exists(): return [] return [ f.stem for f in self.templates_dir.iterdir() if f.is_file() and f.suffix in (".gitignore", ".txt", "") ] def get_template_content(self, language: str) -> str: """Get the raw template content for a language.""" return self.GITIGNORE_TEMPLATES.get(language, "") def append_patterns(self, gitignore_path: Path, patterns: Set[str]) -> None: """Append additional patterns to an existing .gitignore file.""" if gitignore_path.exists(): content = gitignore_path.read_text() if not content.endswith("\n"): content += "\n" else: content = "" content += "\n" + "\n".join(sorted(patterns)) + "\n" gitignore_path.write_text(content)