This commit is contained in:
195
src/config.py
Normal file
195
src/config.py
Normal file
@@ -0,0 +1,195 @@
|
||||
"""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", {}),
|
||||
)
|
||||
Reference in New Issue
Block a user