Files
gitignore-generator/src/config.py
7000pctAUTO 5cc18db1c8
Some checks failed
CI / test (push) Has been cancelled
Initial commit: gitignore-generator v0.1.0
2026-02-01 03:31:33 +00:00

196 lines
6.8 KiB
Python

"""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", {}),
)