"""YAML configuration loading and validation.""" import argparse import sys from dataclasses import dataclass, field from pathlib import Path import yaml @dataclass class ImapConfig: host: str port: int username: str password: str folder: str = "INBOX" ssl: bool = True @dataclass class CaldavConfig: url: str username: str password: str @dataclass class LoggingConfig: level: str = "INFO" @dataclass class Config: imap: ImapConfig caldav: CaldavConfig logging: LoggingConfig = field(default_factory=LoggingConfig) def load_config(path: Path) -> Config: """Load and validate configuration from a YAML file.""" if not path.exists(): print(f"Error: config file not found: {path}", file=sys.stderr) sys.exit(1) with open(path) as f: raw = yaml.safe_load(f) if not isinstance(raw, dict): print("Error: config file must be a YAML mapping", file=sys.stderr) sys.exit(1) for section in ("imap", "caldav"): if section not in raw: print(f"Error: missing required config section: {section}", file=sys.stderr) sys.exit(1) imap_raw = raw["imap"] for key in ("host", "username", "password"): if key not in imap_raw: print(f"Error: missing required imap config key: {key}", file=sys.stderr) sys.exit(1) caldav_raw = raw["caldav"] for key in ("url", "username", "password"): if key not in caldav_raw: print(f"Error: missing required caldav config key: {key}", file=sys.stderr) sys.exit(1) imap_cfg = ImapConfig( host=imap_raw["host"], port=imap_raw.get("port", 993), username=imap_raw["username"], password=imap_raw["password"], folder=imap_raw.get("folder", "INBOX"), ssl=imap_raw.get("ssl", True), ) caldav_cfg = CaldavConfig( url=caldav_raw["url"], username=caldav_raw["username"], password=caldav_raw["password"], ) logging_raw = raw.get("logging", {}) logging_cfg = LoggingConfig( level=logging_raw.get("level", "INFO"), ) return Config(imap=imap_cfg, caldav=caldav_cfg, logging=logging_cfg) def parse_args() -> Path: """Parse CLI arguments and return the config file path.""" parser = argparse.ArgumentParser( description="IMAP IDLE to CalDAV daemon", ) parser.add_argument( "-c", "--config", default="config.yaml", help="Path to YAML config file (default: config.yaml)", ) args = parser.parse_args() return Path(args.config)