"""Template loader module for loading and parsing .gitignore templates.""" from pathlib import Path from typing import Dict, List, Optional, Tuple import yaml TEMPLATE_DIR = Path(__file__).parent / "templates" class TemplateInfo: """Represents a template with its metadata and patterns.""" def __init__(self, name: str, category: str, description: str, patterns: str): self.name = name self.category = category self.description = description self.patterns = patterns def __repr__(self) -> str: return f"TemplateInfo(name={self.name!r}, category={self.category!r})" class TemplateLoader: """Loads and parses .gitignore templates from local storage.""" def __init__(self, template_dir: Optional[Path] = None): self.template_dir = template_dir or TEMPLATE_DIR self._templates_cache: Optional[Dict[str, TemplateInfo]] = None def get_template_path(self, template_name: str) -> Optional[Path]: """Get the file path for a specific template.""" template_file = self.template_dir / f"{template_name}.yaml" if template_file.exists(): return template_file return None def load_template(self, template_name: str) -> Optional[TemplateInfo]: """Load a single template by name.""" template_path = self.get_template_path(template_name) if template_path is None: return None try: with open(template_path, "r", encoding="utf-8") as f: data = yaml.safe_load(f) if data is None: return None return TemplateInfo( name=data.get("name", template_name), category=data.get("category", "uncategorized"), description=data.get("description", ""), patterns=data.get("patterns", ""), ) except (yaml.YAMLError, OSError): return None def load_all_templates(self) -> Dict[str, TemplateInfo]: """Load all available templates from the template directory.""" if self._templates_cache is not None: return self._templates_cache templates: Dict[str, TemplateInfo] = {} for template_file in self.template_dir.glob("*.yaml"): if template_file.name == "index.yaml": continue template_name = template_file.stem template = self.load_template(template_name) if template is not None: templates[template_name] = template self._templates_cache = templates return templates def get_templates_by_category(self) -> Dict[str, List[TemplateInfo]]: """Get all templates grouped by category.""" templates = self.load_all_templates() by_category: Dict[str, List[TemplateInfo]] = {} for template in templates.values(): if template.category not in by_category: by_category[template.category] = [] by_category[template.category].append(template) return by_category def list_template_names(self) -> List[str]: """List all available template names.""" return list(self.load_all_templates().keys()) def get_template_info(self, template_name: str) -> Optional[TemplateInfo]: """Get information about a specific template.""" return self.load_all_templates().get(template_name) def get_similar_templates(self, template_name: str) -> List[str]: """Find templates with similar names to the given template.""" all_names = self.list_template_names() similar: List[str] = [] for name in all_names: if name == template_name: continue if template_name in name or name in template_name: similar.append(name) elif self._levenshtein_distance(template_name, name) <= 2: similar.append(name) return similar[:5] def _levenshtein_distance(self, s1: str, s2: str) -> int: """Calculate the Levenshtein distance between two strings.""" if len(s1) < len(s2): return self._levenshtein_distance(s2, s1) if len(s2) == 0: return len(s1) previous_row = list(range(len(s2) + 1)) for i, c1 in enumerate(s1): current_row = [i + 1] for j, c2 in enumerate(s2): insertions = previous_row[j + 1] + 1 deletions = current_row[j] + 1 substitutions = previous_row[j] + (c1 != c2) current_row.append(min(insertions, deletions, substitutions)) previous_row = current_row return previous_row[-1] def load_template(template_name: str) -> Optional[TemplateInfo]: """Convenience function to load a template by name.""" loader = TemplateLoader() return loader.load_template(template_name) def load_multiple_templates(template_names: List[str]) -> Tuple[List[TemplateInfo], List[str]]: """Load multiple templates, returning loaded templates and missing ones.""" loader = TemplateLoader() loaded: List[TemplateInfo] = [] missing: List[str] = [] for name in template_names: template = loader.load_template(name) if template is not None: loaded.append(template) else: missing.append(name) return loaded, missing