Initial upload: gitignore-cli-generator v1.0.0
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
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
This commit is contained in:
157
src/gitignore_cli/template_loader.py
Normal file
157
src/gitignore_cli/template_loader.py
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user