From 8951f27181e932493b3abd3806f1a963c168f92f Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Fri, 30 Jan 2026 15:34:03 +0000 Subject: [PATCH] Initial upload: gitignore-generator-cli v1.0.0 with CI/CD workflow --- gitignore_generator/api.py | 125 +++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 gitignore_generator/api.py diff --git a/gitignore_generator/api.py b/gitignore_generator/api.py new file mode 100644 index 0000000..6cc818f --- /dev/null +++ b/gitignore_generator/api.py @@ -0,0 +1,125 @@ +"""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