This commit is contained in:
243
src/generator.py
Normal file
243
src/generator.py
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
"""Gitignore generation from templates."""
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Optional, Set
|
||||||
|
|
||||||
|
|
||||||
|
class GitignoreGenerator:
|
||||||
|
"""Generate .gitignore content from templates."""
|
||||||
|
|
||||||
|
TEMPLATE_MAP: Dict[str, str] = {
|
||||||
|
"python": "languages/python.gitignore",
|
||||||
|
"javascript": "languages/javascript.gitignore",
|
||||||
|
"typescript": "languages/typescript.gitignore",
|
||||||
|
"java": "languages/java.gitignore",
|
||||||
|
"go": "languages/go.gitignore",
|
||||||
|
"rust": "languages/rust.gitignore",
|
||||||
|
"dotnet": "languages/dotnet.gitignore",
|
||||||
|
"php": "languages/php.gitignore",
|
||||||
|
"ruby": "languages/ruby.gitignore",
|
||||||
|
"django": "frameworks/django.gitignore",
|
||||||
|
"flask": "frameworks/flask.gitignore",
|
||||||
|
"react": "frameworks/react.gitignore",
|
||||||
|
"vue": "frameworks/vue.gitignore",
|
||||||
|
"angular": "frameworks/angular.gitignore",
|
||||||
|
"rails": "frameworks/rails.gitignore",
|
||||||
|
"laravel": "frameworks/laravel.gitignore",
|
||||||
|
"spring": "frameworks/spring.gitignore",
|
||||||
|
"vscode": "ide/vscode.gitignore",
|
||||||
|
"jetbrains": "ide/jetbrains.gitignore",
|
||||||
|
"visualstudiocode": "ide/visualstudiocode.gitignore",
|
||||||
|
"linux": "os/linux.gitignore",
|
||||||
|
"macos": "os/macos.gitignore",
|
||||||
|
"windows": "os/windows.gitignore",
|
||||||
|
"docker": "tools/docker.gitignore",
|
||||||
|
"gradle": "tools/gradle.gitignore",
|
||||||
|
"maven": "tools/maven.gitignore",
|
||||||
|
"jupyter": "tools/jupyter.gitignore",
|
||||||
|
"terraform": "tools/terraform.gitignore",
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, config: Optional["Config"] = None) -> None:
|
||||||
|
"""Initialize generator with optional configuration."""
|
||||||
|
self.template_dir = Path(__file__).parent / "templates"
|
||||||
|
self.selected_templates: Set[str] = set()
|
||||||
|
self.config = config
|
||||||
|
|
||||||
|
def add_template(self, template_type: str) -> None:
|
||||||
|
"""Add a template type to include."""
|
||||||
|
if template_type in self.TEMPLATE_MAP:
|
||||||
|
self.selected_templates.add(template_type)
|
||||||
|
|
||||||
|
def remove_template(self, template_type: str) -> None:
|
||||||
|
"""Remove a template type from selection."""
|
||||||
|
self.selected_templates.discard(template_type)
|
||||||
|
|
||||||
|
def get_template_path(self, template_type: str) -> Optional[Path]:
|
||||||
|
"""Get the path to a template file."""
|
||||||
|
template_rel = self.TEMPLATE_MAP.get(template_type)
|
||||||
|
if template_rel:
|
||||||
|
template_path = self.template_dir / template_rel
|
||||||
|
if template_path.exists():
|
||||||
|
return template_path
|
||||||
|
return None
|
||||||
|
|
||||||
|
def load_template(self, template_type: str) -> str:
|
||||||
|
"""Load template content."""
|
||||||
|
template_path = self.get_template_path(template_type)
|
||||||
|
if template_path:
|
||||||
|
try:
|
||||||
|
with open(template_path, "r") as f:
|
||||||
|
return f.read()
|
||||||
|
except OSError:
|
||||||
|
return ""
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def load_template_lines(self, template_type: str) -> List[str]:
|
||||||
|
"""Load template content as list of lines."""
|
||||||
|
content = self.load_template(template_type)
|
||||||
|
lines = content.split("\n")
|
||||||
|
while lines and not lines[-1]:
|
||||||
|
lines.pop()
|
||||||
|
while lines and not lines[0]:
|
||||||
|
lines.pop(0)
|
||||||
|
return lines
|
||||||
|
|
||||||
|
def deduplicate_entries(
|
||||||
|
self, entries: List[str], comments: List[str] = None
|
||||||
|
) -> List[str]:
|
||||||
|
"""Remove duplicate entries while preserving order."""
|
||||||
|
seen: Set[str] = set()
|
||||||
|
unique_entries: List[str] = []
|
||||||
|
for entry in entries:
|
||||||
|
normalized = entry.strip().lower()
|
||||||
|
if normalized and normalized not in seen:
|
||||||
|
seen.add(normalized)
|
||||||
|
unique_entries.append(entry)
|
||||||
|
if comments:
|
||||||
|
unique_comments = []
|
||||||
|
seen_comments = set()
|
||||||
|
for comment in comments:
|
||||||
|
if comment not in seen_comments:
|
||||||
|
seen_comments.add(comment)
|
||||||
|
unique_comments.append(comment)
|
||||||
|
return unique_comments + unique_entries
|
||||||
|
return unique_entries
|
||||||
|
|
||||||
|
def generate(self) -> str:
|
||||||
|
"""Generate combined .gitignore content."""
|
||||||
|
all_entries: List[str] = []
|
||||||
|
all_comments: List[str] = []
|
||||||
|
used_categories: Set[str] = set()
|
||||||
|
|
||||||
|
template_order = [
|
||||||
|
"language",
|
||||||
|
"framework",
|
||||||
|
"ide",
|
||||||
|
"os",
|
||||||
|
"tools",
|
||||||
|
]
|
||||||
|
|
||||||
|
category_map = {
|
||||||
|
"python": "language",
|
||||||
|
"javascript": "language",
|
||||||
|
"typescript": "language",
|
||||||
|
"java": "language",
|
||||||
|
"go": "language",
|
||||||
|
"rust": "language",
|
||||||
|
"dotnet": "language",
|
||||||
|
"php": "language",
|
||||||
|
"ruby": "language",
|
||||||
|
"django": "framework",
|
||||||
|
"flask": "framework",
|
||||||
|
"react": "framework",
|
||||||
|
"vue": "framework",
|
||||||
|
"angular": "framework",
|
||||||
|
"rails": "framework",
|
||||||
|
"laravel": "framework",
|
||||||
|
"spring": "framework",
|
||||||
|
"vscode": "ide",
|
||||||
|
"jetbrains": "ide",
|
||||||
|
"visualstudiocode": "ide",
|
||||||
|
"linux": "os",
|
||||||
|
"macos": "os",
|
||||||
|
"windows": "os",
|
||||||
|
"docker": "tools",
|
||||||
|
"gradle": "tools",
|
||||||
|
"maven": "tools",
|
||||||
|
"jupyter": "tools",
|
||||||
|
"terraform": "tools",
|
||||||
|
}
|
||||||
|
|
||||||
|
for template_type in sorted(self.selected_templates):
|
||||||
|
template_path = self.get_template_path(template_type)
|
||||||
|
if template_path:
|
||||||
|
try:
|
||||||
|
category = category_map.get(template_type, "other")
|
||||||
|
header = f"# {template_type.upper()}"
|
||||||
|
if header not in all_comments:
|
||||||
|
all_comments.append(header)
|
||||||
|
|
||||||
|
lines = self.load_template_lines(template_type)
|
||||||
|
all_entries.extend(lines)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not all_entries:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
combined = self.deduplicate_entries(all_entries, all_comments)
|
||||||
|
result = "\n".join(combined)
|
||||||
|
if result and not result.endswith("\n"):
|
||||||
|
result += "\n"
|
||||||
|
return result
|
||||||
|
|
||||||
|
def generate_for_types(
|
||||||
|
self, types: List[str], deduplicate: bool = True
|
||||||
|
) -> str:
|
||||||
|
"""Generate .gitignore for specific types."""
|
||||||
|
self.selected_templates = set()
|
||||||
|
for t in types:
|
||||||
|
self.add_template(t)
|
||||||
|
return self.generate()
|
||||||
|
|
||||||
|
def get_available_templates(self) -> List[str]:
|
||||||
|
"""Get list of available template types."""
|
||||||
|
available = []
|
||||||
|
for template_type, template_rel in self.TEMPLATE_MAP.items():
|
||||||
|
template_path = self.template_dir / template_rel
|
||||||
|
if template_path.exists():
|
||||||
|
available.append(template_type)
|
||||||
|
return sorted(available)
|
||||||
|
|
||||||
|
def validate_template_type(self, template_type: str) -> bool:
|
||||||
|
"""Validate if a template type is valid."""
|
||||||
|
return template_type in self.TEMPLATE_MAP
|
||||||
|
|
||||||
|
def get_template_categories(self) -> Dict[str, List[str]]:
|
||||||
|
"""Get templates organized by category."""
|
||||||
|
categories: Dict[str, List[str]] = {
|
||||||
|
"language": [],
|
||||||
|
"framework": [],
|
||||||
|
"ide": [],
|
||||||
|
"os": [],
|
||||||
|
"tools": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
category_map = {
|
||||||
|
"python": "language",
|
||||||
|
"javascript": "language",
|
||||||
|
"typescript": "language",
|
||||||
|
"java": "language",
|
||||||
|
"go": "language",
|
||||||
|
"rust": "language",
|
||||||
|
"dotnet": "language",
|
||||||
|
"php": "language",
|
||||||
|
"ruby": "language",
|
||||||
|
"django": "framework",
|
||||||
|
"flask": "framework",
|
||||||
|
"react": "framework",
|
||||||
|
"vue": "framework",
|
||||||
|
"angular": "framework",
|
||||||
|
"rails": "framework",
|
||||||
|
"laravel": "framework",
|
||||||
|
"spring": "framework",
|
||||||
|
"vscode": "ide",
|
||||||
|
"jetbrains": "ide",
|
||||||
|
"visualstudiocode": "ide",
|
||||||
|
"linux": "os",
|
||||||
|
"macos": "os",
|
||||||
|
"windows": "os",
|
||||||
|
"docker": "tools",
|
||||||
|
"gradle": "tools",
|
||||||
|
"maven": "tools",
|
||||||
|
"jupyter": "tools",
|
||||||
|
"terraform": "tools",
|
||||||
|
}
|
||||||
|
|
||||||
|
for template_type in self.TEMPLATE_MAP:
|
||||||
|
category = category_map.get(template_type, "other")
|
||||||
|
if category in categories:
|
||||||
|
categories[category].append(template_type)
|
||||||
|
|
||||||
|
return categories
|
||||||
Reference in New Issue
Block a user