"""Configuration management for gitignore-generator.""" import os from pathlib import Path from typing import Any, Dict, List, Optional class Config: """Configuration management class.""" DEFAULT_CONFIG_NAME = ".gitignorerc" def __init__( self, custom_templates: Optional[Dict[str, str]] = None, default_types: Optional[List[str]] = None, exclude_patterns: Optional[List[str]] = None, include_patterns: Optional[List[str]] = None, ) -> None: """Initialize configuration.""" self.custom_templates = custom_templates or {} self.default_types = default_types or [] self.exclude_patterns = exclude_patterns or [] self.include_patterns = include_patterns or {} @classmethod def load(cls, config_path: str) -> "Config": """Load configuration from YAML file.""" import yaml path = Path(config_path) if not path.exists(): raise FileNotFoundError(f"Config file not found: {config_path}") try: with open(path, "r") as f: data = yaml.safe_load(f) except yaml.YAMLError as e: raise ValueError(f"Invalid YAML in config file: {e}") except OSError as e: raise OSError(f"Error reading config file: {e}") if not isinstance(data, dict): raise ValueError("Config file must contain a YAML dictionary") custom_templates = data.get("custom_templates", {}) default_types = data.get("default_types", []) exclude_patterns = data.get("exclude_patterns", []) include_patterns = data.get("include_patterns", {}) return cls( custom_templates=custom_templates, default_types=default_types, exclude_patterns=exclude_patterns, include_patterns=include_patterns, ) @classmethod def find_config( cls, search_paths: Optional[List[str]] = None ) -> Optional[Path]: """Find configuration file in search paths.""" if search_paths is None: search_paths = [ os.getcwd(), os.path.expanduser("~"), "/etc", ] for path_str in search_paths: path = Path(path_str) config_path = path / cls.DEFAULT_CONFIG_NAME if config_path.exists(): return config_path yaml_path = path / "config.yaml" if yaml_path.exists(): return yaml_path return None def save(self, config_path: str) -> None: """Save configuration to YAML file.""" import yaml path = Path(config_path) data = { "custom_templates": self.custom_templates, "default_types": self.default_types, "exclude_patterns": self.exclude_patterns, "include_patterns": self.include_patterns, } try: with open(path, "w") as f: yaml.dump(data, f, default_flow_style=False, indent=2) except OSError as e: raise OSError(f"Error writing config file: {e}") def get(self, key: str, default: Any = None) -> Any: """Get configuration value.""" if hasattr(self, key): return getattr(self, key) return default def set(self, key: str, value: Any) -> None: """Set configuration value.""" if hasattr(self, key): setattr(self, key, value) def merge(self, other: "Config") -> "Config": """Merge with another configuration.""" return Config( custom_templates={ **self.custom_templates, **other.custom_templates, }, default_types=self.default_types or other.default_types, exclude_patterns=list( set(self.exclude_patterns + other.exclude_patterns) ), include_patterns={ **self.include_patterns, **other.include_patterns, }, ) def validate(self) -> List[str]: """Validate configuration and return list of errors.""" errors = [] if not isinstance(self.custom_templates, dict): errors.append("custom_templates must be a dictionary") else: for key, value in self.custom_templates.items(): if not isinstance(key, str): errors.append(f"Template key must be string: {key}") if not isinstance(value, str): errors.append( f"Template value for '{key}' must be a string" ) if not isinstance(self.default_types, list): errors.append("default_types must be a list") else: for item in self.default_types: if not isinstance(item, str): errors.append(f"Default type must be string: {item}") if not isinstance(self.exclude_patterns, list): errors.append("exclude_patterns must be a list") else: for item in self.exclude_patterns: if not isinstance(item, str): errors.append( f"Exclude pattern must be string: {item}" ) if not isinstance(self.include_patterns, dict): errors.append("include_patterns must be a dictionary") else: for key, value in self.include_patterns.items(): if not isinstance(key, str): errors.append(f"Include pattern key must be string: {key}") if isinstance(value, list): for item in value: if not isinstance(item, str): errors.append( f"Include pattern value for '{key}' must be string: {item}" ) elif not isinstance(value, str): errors.append( f"Include pattern value for '{key}' must be string or list" ) return errors def to_dict(self) -> Dict[str, Any]: """Convert configuration to dictionary.""" return { "custom_templates": self.custom_templates, "default_types": self.default_types, "exclude_patterns": self.exclude_patterns, "include_patterns": self.include_patterns, } @classmethod def from_dict(cls, data: Dict[str, Any]) -> "Config": """Create configuration from dictionary.""" return cls( custom_templates=data.get("custom_templates", {}), default_types=data.get("default_types", []), exclude_patterns=data.get("exclude_patterns", []), include_patterns=data.get("include_patterns", {}), )