Thomas Faour 37b032771f Initial commit: IMAP IDLE to CalDAV daemon
Monitor an IMAP mailbox via IDLE for iMIP emails (invitations, RSVPs,
cancellations) and ICS attachments, then update a CalDAV calendar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 19:40:17 -05:00

105 lines
2.6 KiB
Python

"""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)