diff --git a/project_scaffold_cli/gitignore.py b/project_scaffold_cli/gitignore.py new file mode 100644 index 0000000..4cc5397 --- /dev/null +++ b/project_scaffold_cli/gitignore.py @@ -0,0 +1,203 @@ +"""Gitignore generator for Project Scaffold CLI.""" + +import os +from pathlib import Path +from typing import Dict, 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)