feat: add configuration module with YAML loading and defaults

Made-with: Cursor
This commit is contained in:
cottongin
2026-03-10 02:26:11 -04:00
parent 0c48edb5f7
commit db94c25b44
7 changed files with 128 additions and 0 deletions

Binary file not shown.

View File

@@ -0,0 +1,72 @@
from dataclasses import dataclass
from pathlib import Path
from typing import Any
import yaml
@dataclass
class Config:
pulse_color: str = "#FF9500"
pulse_width: int = 4
pulse_speed: float = 1.5
pulse_opacity_min: float = 0.3
pulse_opacity_max: float = 1.0
sound_enabled: bool = True
sound_name: str = "Glass"
sound_volume: float = 0.5
poll_interval: float = 0.5
cooldown: float = 3.0
auto_dismiss: int = 300
FIELD_MAP: dict[str, dict[str, str]] = {
"pulse": {
"color": "pulse_color",
"width": "pulse_width",
"speed": "pulse_speed",
"opacity_min": "pulse_opacity_min",
"opacity_max": "pulse_opacity_max",
},
"sound": {
"enabled": "sound_enabled",
"name": "sound_name",
"volume": "sound_volume",
},
"detection": {
"poll_interval": "poll_interval",
"cooldown": "cooldown",
},
"timeout": {
"auto_dismiss": "auto_dismiss",
},
}
DEFAULT_CONFIG_PATH = Path.home() / ".cursor-flasher" / "config.yaml"
def load_config(path: Path = DEFAULT_CONFIG_PATH) -> Config:
"""Load config from YAML, falling back to defaults for missing values."""
if not path.exists():
return Config()
with open(path) as f:
raw = yaml.safe_load(f)
if not raw or not isinstance(raw, dict):
return Config()
overrides: dict[str, Any] = {}
for section, mapping in FIELD_MAP.items():
section_data = raw.get(section, {})
if not isinstance(section_data, dict):
continue
for yaml_key, field_name in mapping.items():
if yaml_key in section_data:
overrides[field_name] = section_data[yaml_key]
return Config(**overrides)

Binary file not shown.

56
tests/test_config.py Normal file
View File

@@ -0,0 +1,56 @@
import pytest
from pathlib import Path
from cursor_flasher.config import Config, load_config, DEFAULT_CONFIG_PATH
class TestDefaultConfig:
def test_has_pulse_settings(self):
cfg = Config()
assert cfg.pulse_color == "#FF9500"
assert cfg.pulse_width == 4
assert cfg.pulse_speed == 1.5
assert cfg.pulse_opacity_min == 0.3
assert cfg.pulse_opacity_max == 1.0
def test_has_sound_settings(self):
cfg = Config()
assert cfg.sound_enabled is True
assert cfg.sound_name == "Glass"
assert cfg.sound_volume == 0.5
def test_has_detection_settings(self):
cfg = Config()
assert cfg.poll_interval == 0.5
assert cfg.cooldown == 3.0
def test_has_timeout_settings(self):
cfg = Config()
assert cfg.auto_dismiss == 300
class TestLoadConfig:
def test_loads_from_yaml(self, tmp_path):
config_file = tmp_path / "config.yaml"
config_file.write_text(
"pulse:\n"
' color: "#00FF00"\n'
" width: 8\n"
"sound:\n"
" enabled: false\n"
)
cfg = load_config(config_file)
assert cfg.pulse_color == "#00FF00"
assert cfg.pulse_width == 8
assert cfg.sound_enabled is False
assert cfg.pulse_speed == 1.5
assert cfg.sound_name == "Glass"
def test_missing_file_returns_defaults(self, tmp_path):
cfg = load_config(tmp_path / "nonexistent.yaml")
assert cfg.pulse_color == "#FF9500"
def test_empty_file_returns_defaults(self, tmp_path):
config_file = tmp_path / "config.yaml"
config_file.write_text("")
cfg = load_config(config_file)
assert cfg.pulse_color == "#FF9500"