"""API client for gitignore.io.""" from pathlib import Path from typing import Optional import requests BASE_URL = "https://www.toptal.com/developers/gitignore/api" CACHE_DIR = Path.home() / ".cache" / "gitignore-generator" TEMPLATES_DIR = Path(__file__).parent.parent / "templates" class GitignoreIOError(Exception): """Exception raised for gitignore.io API errors.""" pass def get_patterns(tech: str, force_refresh: bool = False) -> str: """Fetch gitignore patterns for a single technology. Args: tech: Technology name (e.g., 'node', 'python', 'django') force_refresh: Force refresh from API instead of using cache Returns: Gitignore patterns as a string Raises: GitignoreIOError: If the API request fails """ tech_lower = tech.lower() cache_file = CACHE_DIR / f"{tech_lower}.txt" if not force_refresh and cache_file.exists(): cache_data = cache_file.read_text() return cache_data try: response = requests.get(f"{BASE_URL}/{tech_lower}", timeout=30) response.raise_for_status() patterns = response.text CACHE_DIR.mkdir(parents=True, exist_ok=True) cache_file.write_text(patterns) return patterns except requests.RequestException as e: fallback = get_local_template(tech_lower) if fallback: return fallback raise GitignoreIOError(f"Failed to fetch patterns for '{tech}': {e}") def get_patterns_batch(techs: list[str], force_refresh: bool = False) -> dict[str, str]: """Fetch gitignore patterns for multiple technologies. Args: techs: List of technology names force_refresh: Force refresh from API instead of using cache Returns: Dictionary mapping technology names to their patterns """ patterns = {} for tech in techs: try: patterns[tech] = get_patterns(tech, force_refresh) except GitignoreIOError: patterns[tech] = "" return patterns def get_list(force_refresh: bool = False) -> list[str]: """Get list of all available technology stacks from gitignore.io. Args: force_refresh: Force refresh from API instead of using cache Returns: List of available technology names """ cache_file = CACHE_DIR / "list.txt" if not force_refresh and cache_file.exists(): cache_data = cache_file.read_text() return cache_data.strip().split('\n') try: response = requests.get(f"{BASE_URL}/list", timeout=30) response.raise_for_status() lines = response.text.strip().split('\n') techs = [line.split(',')[0] for line in lines if line] CACHE_DIR.mkdir(parents=True, exist_ok=True) cache_file.write_text(response.text) return techs except requests.RequestException as e: local_list = get_local_list() if local_list: return local_list raise GitignoreIOError(f"Failed to fetch list: {e}") def get_local_template(tech: str) -> Optional[str]: """Get local template for a technology as fallback. Args: tech: Technology name Returns: Template content or None if not found """ template_file = TEMPLATES_DIR / f"{tech.lower()}.gitignore" if template_file.exists(): return template_file.read_text() return None def get_local_list() -> Optional[list[str]]: """Get list of locally available templates. Returns: List of available technology names or None """ list_file = TEMPLATES_DIR / "list.txt" if list_file.exists(): return list_file.read_text().strip().split('\n') return None