"""Configuration management for local-commit-message-generator.""" import os from pathlib import Path from typing import Any, Dict, Optional import tomlkit class ConfigError(Exception): """Raised when configuration errors occur.""" pass DEFAULT_TYPE_RULES: Dict[str, list[str]] = { "feat": ["src/", "lib/", "app/", "controllers/", "models/"], "fix": ["src/", "lib/", "bug", "fix", "issue", "hotfix"], "docs": [".md", ".rst", "docs/", "documentation/"], "style": [".css", ".scss", ".sass", ".less", "styles/"], "refactor": ["refactor/", "rewrite/", "restructure/"], "test": ["test/", "tests/", "__tests__/", ".test.", ".spec."], "chore": ["package.json", "pyproject.toml", "requirements", ".gitignore", "Makefile"], "perf": ["performance/", "perf/", "optimize/", "optimization/"], "ci": [".github/", ".gitlab-ci.yml", ".travis.yml", "Jenkinsfile", "tox.ini"], "build": ["build/", "webpack/", "vite.config", "babel.config", "rollup.config"], } DEFAULT_CONFIG: Dict[str, Any] = { "type_rules": DEFAULT_TYPE_RULES, "template": "{type}{scope}: {description}", "scopes": {}, "description_length": 72, "max_files": 5, "include_file_list": True, "file_list_template": "\n\nFiles changed:\n{files}", } def get_config_path() -> Path: """Get the path to the user configuration file.""" home = Path.home() return home / ".local_commit_gen.toml" def load_config(config_path: Optional[Path] = None) -> Dict[str, Any]: """Load configuration from file. Args: config_path: Optional path to config file. If not provided, uses default path. Returns: Dictionary containing configuration. """ if config_path is None: config_path = get_config_path() if not config_path.exists(): return DEFAULT_CONFIG.copy() try: with open(config_path, "r") as f: config = tomlkit.parse(f.read()) except tomlkit.exceptions.ParseError as e: raise ConfigError(f"Invalid TOML syntax in config file: {e}") merged = DEFAULT_CONFIG.copy() for key, value in config.items(): if key == "type_rules" and isinstance(value, dict): merged["type_rules"] = {**DEFAULT_TYPE_RULES, **value} else: merged[key] = value return merged def save_config(config: Dict[str, Any], config_path: Optional[Path] = None) -> None: """Save configuration to file. Args: config: Configuration dictionary to save. config_path: Optional path to config file. If not provided, uses default path. """ if config_path is None: config_path = get_config_path() try: with open(config_path, "w") as f: tomlkit.dump(config, f) except OSError as e: raise ConfigError(f"Failed to write config file: {e}") def ensure_config_exists() -> None: """Ensure default config file exists.""" config_path = get_config_path() if not config_path.exists(): save_config(DEFAULT_CONFIG.copy()) def get_type_rules(config: Optional[Dict[str, Any]] = None) -> Dict[str, list[str]]: """Get type rules from configuration. Args: config: Optional configuration dictionary. If not provided, loads from file. Returns: Dictionary mapping commit types to patterns. """ if config is None: config = load_config() return config.get("type_rules", DEFAULT_TYPE_RULES) def get_template(config: Optional[Dict[str, Any]] = None) -> str: """Get message template from configuration. Args: config: Optional configuration dictionary. If not provided, loads from file. Returns: Message template string. """ if config is None: config = load_config() return config.get("template", DEFAULT_CONFIG["template"])