move and rename module

This commit is contained in:
Bendt
2025-12-18 14:00:54 -05:00
parent 37be42884f
commit fe65183fb7
33 changed files with 26 additions and 24 deletions

179
src/mail/config.py Normal file
View File

@@ -0,0 +1,179 @@
"""Configuration system for Mail email reader using Pydantic."""
import logging
import os
from pathlib import Path
from typing import Literal, Optional
import toml
from pydantic import BaseModel, Field
logger = logging.getLogger(__name__)
class TaskBackendConfig(BaseModel):
"""Configuration for task management backend."""
backend: Literal["taskwarrior", "dstask"] = "taskwarrior"
taskwarrior_path: str = "task"
dstask_path: str = Field(
default_factory=lambda: str(Path.home() / ".local" / "bin" / "dstask")
)
class EnvelopeDisplayConfig(BaseModel):
"""Configuration for envelope list item rendering."""
# Sender display
max_sender_length: int = 25
# Date/time display
date_format: str = "%m/%d"
time_format: str = "%H:%M"
show_date: bool = True
show_time: bool = True
# Grouping
group_by: Literal["relative", "absolute"] = "relative"
# relative: "Today", "Yesterday", "This Week", etc.
# absolute: "December 2025", "November 2025", etc.
# Layout
lines: Literal[2, 3] = 2
# 2: sender/date on line 1, subject on line 2
# 3: sender/date on line 1, subject on line 2, preview on line 3
show_checkbox: bool = True
show_preview: bool = False # Only used when lines=3
# NerdFont icons for status
icon_unread: str = "\uf0e0" # nf-fa-envelope (filled)
icon_read: str = "\uf2b6" # nf-fa-envelope_open (open)
icon_flagged: str = "\uf024" # nf-fa-flag
icon_attachment: str = "\uf0c6" # nf-fa-paperclip
class KeybindingsConfig(BaseModel):
"""Keybinding customization."""
next_message: str = "j"
prev_message: str = "k"
delete: str = "#"
archive: str = "e"
open_by_id: str = "o"
quit: str = "q"
toggle_header: str = "h"
create_task: str = "t"
reload: str = "%"
toggle_sort: str = "s"
toggle_selection: str = "space"
clear_selection: str = "escape"
scroll_page_down: str = "pagedown"
scroll_page_down: str = "space"
scroll_page_up: str = "b"
toggle_main_content: str = "w"
open_links: str = "l"
toggle_view_mode: str = "m"
class ContentDisplayConfig(BaseModel):
"""Configuration for message content display."""
# View mode: "markdown" for pretty rendering, "html" for raw/plain display
default_view_mode: Literal["markdown", "html"] = "markdown"
class LinkPanelConfig(BaseModel):
"""Configuration for the link panel."""
# Whether to close the panel after opening a link
close_on_open: bool = False
class MailOperationsConfig(BaseModel):
"""Configuration for mail operations."""
# Folder to move messages to when archiving
archive_folder: str = "Archive"
class ThemeConfig(BaseModel):
"""Theme/appearance settings."""
theme_name: str = "monokai"
class MailAppConfig(BaseModel):
"""Main configuration for Mail email reader."""
task: TaskBackendConfig = Field(default_factory=TaskBackendConfig)
envelope_display: EnvelopeDisplayConfig = Field(
default_factory=EnvelopeDisplayConfig
)
content_display: ContentDisplayConfig = Field(default_factory=ContentDisplayConfig)
link_panel: LinkPanelConfig = Field(default_factory=LinkPanelConfig)
mail: MailOperationsConfig = Field(default_factory=MailOperationsConfig)
keybindings: KeybindingsConfig = Field(default_factory=KeybindingsConfig)
theme: ThemeConfig = Field(default_factory=ThemeConfig)
@classmethod
def get_config_path(cls) -> Path:
"""Get the path to the config file."""
# Check environment variable first
env_path = os.getenv("LUK_MAIL_CONFIG")
if env_path:
return Path(env_path)
# Default to ~/.config/luk/mail.toml
return Path.home() / ".config" / "luk" / "mail.toml"
@classmethod
def load(cls, config_path: Optional[Path] = None) -> "MailAppConfig":
"""Load config from TOML file with defaults for missing values."""
if config_path is None:
config_path = cls.get_config_path()
if config_path.exists():
try:
with open(config_path, "r") as f:
data = toml.load(f)
logger.info(f"Loaded config from {config_path}")
return cls.model_validate(data)
except Exception as e:
logger.warning(f"Error loading config from {config_path}: {e}")
logger.warning("Using default configuration")
return cls()
else:
logger.info(f"No config file at {config_path}, using defaults")
return cls()
def save(self, config_path: Optional[Path] = None) -> None:
"""Save current config to TOML file."""
if config_path is None:
config_path = self.get_config_path()
# Ensure parent directory exists
config_path.parent.mkdir(parents=True, exist_ok=True)
with open(config_path, "w") as f:
toml.dump(self.model_dump(), f)
logger.info(f"Saved config to {config_path}")
# Global config instance (lazy-loaded)
_config: Optional[MailAppConfig] = None
def get_config() -> MailAppConfig:
"""Get the global config instance, loading it if necessary."""
global _config
if _config is None:
_config = MailAppConfig.load()
return _config
def reload_config() -> MailAppConfig:
"""Force reload of the config from disk."""
global _config
_config = MailAppConfig.load()
return _config