From e90697b7d38f2bdee008cdf4cddad7fcbcf28080 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Thu, 29 Jan 2026 15:42:17 +0000 Subject: [PATCH] Initial upload: gitignore-cli-generator v1.0.0 --- src/gitignore_cli/template_loader.py | 157 +++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 src/gitignore_cli/template_loader.py diff --git a/src/gitignore_cli/template_loader.py b/src/gitignore_cli/template_loader.py new file mode 100644 index 0000000..b8b539f --- /dev/null +++ b/src/gitignore_cli/template_loader.py @@ -0,0 +1,157 @@ +"""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