#!/usr/bin/env python3 """ Configuration file handler for QLPyCon Supports loading from ~/.qlpycon.conf or ./qlpycon.conf """ import os import configparser import logging logger = logging.getLogger('config_loader') class ConfigLoader: """Load configuration from INI file""" def __init__(self): self.config = configparser.ConfigParser() self.config_loaded = False def load(self): """ Try to load config from (in order): 1. ./qlpycon.conf (current directory) 2. ~/.qlpycon.conf (home directory) """ config_paths = [ 'qlpycon.conf', os.path.expanduser('~/.qlpycon.conf') ] for path in config_paths: if os.path.exists(path): try: self.config.read(path) self.config_loaded = True logger.info(f'Loaded configuration from: {path}') return True except Exception as e: logger.warning(f'Failed to load config from {path}: {e}') logger.debug('No configuration file found, using defaults') return False def get(self, section, key, fallback=None): """Get a configuration value""" if not self.config_loaded: return fallback try: return self.config.get(section, key, fallback=fallback) except (configparser.NoSectionError, configparser.NoOptionError): return fallback def get_int(self, section, key, fallback=0): """Get an integer configuration value""" value = self.get(section, key) if value is None: return fallback try: return int(value) except ValueError: logger.warning(f'Invalid integer value for [{section}] {key}: {value}') return fallback def get_bool(self, section, key, fallback=False): """Get a boolean configuration value""" value = self.get(section, key) if value is None: return fallback return value.lower() in ('true', 'yes', '1', 'on') def get_host(self): """Get connection host""" return self.get('connection', 'host') def get_password(self): """Get connection password (supports ${ENV_VAR} syntax)""" password = self.get('connection', 'password') if not password: return None # Support environment variable substitution: ${VAR_NAME} if password.startswith('${') and password.endswith('}'): env_var = password[2:-1] return os.environ.get(env_var) return password def get_log_level(self): """Get logging level""" level_str = self.get('logging', 'level', 'INFO') levels = { 'DEBUG': logging.DEBUG, 'INFO': logging.INFO, 'WARNING': logging.WARNING, 'ERROR': logging.ERROR, 'CRITICAL': logging.CRITICAL } return levels.get(level_str.upper(), logging.INFO) def create_example_config(): """Create an example configuration file""" config_content = """# QLPyCon Configuration File # Place this file as ~/.qlpycon.conf or ./qlpycon.conf [connection] # Server connection settings host = tcp://10.13.12.93:28969 # Use ${ENV_VAR} to read from environment password = ${QLPYCON_PASSWORD} [logging] # Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL level = INFO [ui] # Max command history entries max_history = 10 # Color scheme (future feature) color_scheme = quake [behavior] # Quit confirmation timeout (seconds) quit_timeout = 3.0 # Player respawn delay (seconds) respawn_delay = 3.0 """ example_path = os.path.expanduser('~/.qlpycon.conf.example') try: with open(example_path, 'w') as f: f.write(config_content) print(f'Created example config: {example_path}') print(f'Copy to ~/.qlpycon.conf and edit as needed') return True except Exception as e: print(f'Failed to create example config: {e}') return False if __name__ == '__main__': # Create example config when run directly create_example_config()