"""Configuration management module for DaemonControl. This module provides a singleton ConfigManager class that handles loading and accessing configuration settings from JSON files. """ import json import os from pathlib import Path from typing import Any, Optional class ConfigManager: """Singleton configuration manager. Loads configuration from ~/.config/daemon-control/config.json or uses defaults if the file doesn't exist. Supports path expansion for ~ and environment variables. """ _instance: Optional['ConfigManager'] = None def __new__(cls) -> 'ConfigManager': """Ensure only one instance exists (singleton pattern).""" if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance def __init__(self) -> None: """Initialize the configuration manager. Loads configuration from file or creates default configuration. Only initializes once due to singleton pattern. """ # Avoid re-initialization for singleton if hasattr(self, '_initialized'): return self._config: dict = {} self._config_file: Path = Path.home() / '.config' / 'daemon-control' / 'config.json' self._load_config() self._initialized = True def get(self, section: str, key: str, default: Any = None) -> Any: """Get configuration value. Args: section: Configuration section (e.g., 'database') key: Configuration key (e.g., 'path') default: Default value if key not found Returns: Configuration value, with paths expanded if applicable """ try: value = self._config[section][key] # Expand paths if value is a string if isinstance(value, str) and ('/' in value or '~' in value): return self._expand_path(value) return value except KeyError: return default def _expand_path(self, path: str) -> str: """Expand ~ and environment variables in path. Args: path: Path string possibly containing ~ or ${VAR} Returns: Expanded absolute path """ # Expand environment variables like ${HOME} expanded = os.path.expandvars(path) # Expand ~ to user home directory expanded = os.path.expanduser(expanded) return expanded def _load_config(self) -> None: """Load configuration from file or create default. If config file doesn't exist, creates it with default values. Creates necessary directories if they don't exist. """ # Default configuration default_config = { "database": { "path": "~/.config/daemon-control/daemon.db" }, "logging": { "level": "INFO", "daemon_log_file": "~/.config/daemon-control/logs/daemon.log", "execution_log_dir": "~/.config/daemon-control/logs/executions", "retention_days": 30, "rotation": "daily" }, "daemon": { "max_concurrent_jobs": 3, "default_timeout": 3600, "check_interval": 60 } } if self._config_file.exists(): # Load from existing file try: with open(self._config_file, 'r') as f: self._config = json.load(f) except (json.JSONDecodeError, IOError) as e: # If file is corrupted, use defaults print(f"Warning: Could not load config file: {e}. Using defaults.") self._config = default_config else: # Create config directory and file with defaults self._config_file.parent.mkdir(parents=True, exist_ok=True) self._config = default_config try: with open(self._config_file, 'w') as f: json.dump(self._config, f, indent=2) except IOError as e: print(f"Warning: Could not write config file: {e}")