Files
gitignore-cli-generator/src/gitignore_cli/template_loader.py
7000pctAUTO e90697b7d3
Some checks failed
CI / test (3.10) (push) Has been cancelled
CI / test (3.11) (push) Has been cancelled
CI / test (3.12) (push) Has been cancelled
CI / test (3.9) (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / type-check (push) Has been cancelled
CI / build (push) Has been cancelled
Initial upload: gitignore-cli-generator v1.0.0
2026-01-29 15:42:17 +00:00

158 lines
5.3 KiB
Python

"""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