""" Configuration management module """ import os import sys import shutil from pathlib import Path from typing import Optional class ConfigManager: def __init__(self): self.config_dir = self._get_config_dir() self.env_file = self.config_dir / '.env' self.templates_dir = self.config_dir / 'templates' self.rules_dir = self.config_dir / 'rules' self.prompts_dir = self.config_dir / 'prompts' def _get_config_dir(self) -> Path: """Get platform-specific config directory""" if sys.platform == 'win32': # Windows: %USERPROFILE%\.config\madomeda base = Path(os.environ.get('USERPROFILE', os.path.expanduser('~'))) config_dir = base / '.config' / 'madomeda' elif sys.platform == 'darwin': # macOS: ~/.config/madomeda config_dir = Path.home() / '.config' / 'madomeda' else: # Linux: ~/.config/madomeda config_dir = Path.home() / '.config' / 'madomeda' return config_dir def ensure_config_exists(self): """Ensure configuration directory exists with default files""" if not self.config_dir.exists(): self.config_dir.mkdir(parents=True, exist_ok=True) self._create_default_config() # Ensure subdirectories exist self.templates_dir.mkdir(exist_ok=True) self.rules_dir.mkdir(exist_ok=True) self.prompts_dir.mkdir(exist_ok=True) # Copy defaults if they don't exist if not (self.templates_dir / 'default.json').exists(): self._create_default_template() if not list(self.rules_dir.glob('*.json')): self._create_default_rules() if not (self.prompts_dir / 'default.txt').exists(): self._create_default_prompt() def _create_default_config(self): """Create default .env file""" default_env = """# Madomeda Configuration # OpenAI-compatible API settings # API endpoint URL OPENAI_API_URL=https://api.openai.com/v1 # Model to use OPENAI_MODEL=gpt-4 # API Key options: # 1. Empty string "" = No API key needed (LLM features disabled) # 2. $ENV_VAR_NAME = Read from environment variable # 3. Direct key = Use the key (WARNING: security risk in plain text) OPENAI_API_KEY="" """ with open(self.env_file, 'w', encoding='utf-8') as f: f.write(default_env) def _create_default_template(self): """Create default template""" import json default_template = { "title": "heur|orig", "created": "heur|orig", "changed": "heur|orig", "authors": "heur|orig", "version": "heur|orig", "tags": "heur|orig|ai default" } with open(self.templates_dir / 'default.json', 'w', encoding='utf-8') as f: json.dump(default_template, f, indent=2) def _create_default_rules(self): """Create default rules""" import json rules = { 'tags_normalization.json': { "name": "tags_normalization", "description": "Normalize tags to lowercase with underscores", "type": "tags" }, 'tag_to_tags.json': { "name": "tag_to_tags", "description": "Rename 'tag' key to 'tags'", "type": "rename" }, 'lowercase_keys.json': { "name": "lowercase_keys", "description": "All frontmatter keys must be lowercase", "type": "keys" }, 'summary_to_description.json': { "name": "summary_to_description", "description": "Rename 'summary' key to 'description'", "type": "rename" } } for filename, rule_data in rules.items(): with open(self.rules_dir / filename, 'w', encoding='utf-8') as f: json.dump(rule_data, f, indent=2) def _create_default_prompt(self): """Create default prompt""" default_prompt = """You are a metadata extraction assistant for markdown documents. Your task is to analyze the document content and suggest appropriate frontmatter metadata. Guidelines: - For tags: prefer selecting from the provided existing tags list when appropriate - You may create new tags if they better fit the content - New tags must follow the rules: lowercase letters, numbers, and underscores only - Be concise and accurate - Preserve important existing metadata when present""" with open(self.prompts_dir / 'default.txt', 'w', encoding='utf-8') as f: f.write(default_prompt) def get_api_config(self) -> dict: """Get API configuration with environment variable resolution""" config = { 'url': 'https://api.openai.com/v1', 'model': 'gpt-4', 'api_key': '', 'api_key_source': 'none' } if not self.env_file.exists(): return config # Parse .env file env_vars = {} with open(self.env_file, 'r', encoding='utf-8') as f: for line in f: line = line.strip() if not line or line.startswith('#'): continue if '=' in line: key, value = line.split('=', 1) env_vars[key.strip()] = value.strip() # Get URL if 'OPENAI_API_URL' in env_vars: config['url'] = env_vars['OPENAI_API_URL'] # Get model if 'OPENAI_MODEL' in env_vars: config['model'] = env_vars['OPENAI_MODEL'] # Get API key with special handling if 'OPENAI_API_KEY' in env_vars: api_key_value = env_vars['OPENAI_API_KEY'] # Remove quotes if present api_key_value = api_key_value.strip('"').strip("'") if not api_key_value: # Empty string = no key needed config['api_key'] = '' config['api_key_source'] = 'none' elif api_key_value.startswith('$'): # Environment variable reference env_var_name = api_key_value[1:] env_value = os.environ.get(env_var_name) if env_value is None: raise ValueError( f"Environment variable '{env_var_name}' not found. " f"Referenced in .env as OPENAI_API_KEY={api_key_value}" ) config['api_key'] = env_value config['api_key_source'] = f'env:{env_var_name}' else: # Plain text key (security warning will be issued) config['api_key'] = api_key_value config['api_key_source'] = 'plaintext' return config def print_config_info(self): """Print configuration location and status""" print(f"Configuration directory: {self.config_dir}") print(f" .env file: {self.env_file}") print(f" Templates: {self.templates_dir}") print(f" Rules: {self.rules_dir}") print(f" Prompts: {self.prompts_dir}") # Get API config to show status try: api_config = self.get_api_config() if api_config['api_key_source'] == 'none': print(f" API Key: Not configured (LLM features disabled)") elif api_config['api_key_source'] == 'plaintext': print(f" API Key: Configured (plain text)") print(f" WARNING: Storing API keys in plain text is a security risk!") print(f" Consider using environment variable: OPENAI_API_KEY=$YOUR_ENV_VAR") elif api_config['api_key_source'].startswith('env:'): env_var = api_config['api_key_source'].split(':', 1)[1] print(f" API Key: From environment variable ${env_var}") except ValueError as e: print(f" API Key: ERROR - {e}") print()