diff --git a/src/cursor_flasher/__pycache__/__init__.cpython-312.pyc b/src/cursor_flasher/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..67eb6b3 Binary files /dev/null and b/src/cursor_flasher/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/cursor_flasher/__pycache__/config.cpython-312.pyc b/src/cursor_flasher/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000..6f549e9 Binary files /dev/null and b/src/cursor_flasher/__pycache__/config.cpython-312.pyc differ diff --git a/src/cursor_flasher/config.py b/src/cursor_flasher/config.py new file mode 100644 index 0000000..21d6a2f --- /dev/null +++ b/src/cursor_flasher/config.py @@ -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) diff --git a/tests/__pycache__/__init__.cpython-312.pyc b/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..d552ce7 Binary files /dev/null and b/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/tests/__pycache__/conftest.cpython-312-pytest-8.3.4.pyc b/tests/__pycache__/conftest.cpython-312-pytest-8.3.4.pyc new file mode 100644 index 0000000..1d02b3f Binary files /dev/null and b/tests/__pycache__/conftest.cpython-312-pytest-8.3.4.pyc differ diff --git a/tests/__pycache__/test_config.cpython-312-pytest-8.3.4.pyc b/tests/__pycache__/test_config.cpython-312-pytest-8.3.4.pyc new file mode 100644 index 0000000..207c9d3 Binary files /dev/null and b/tests/__pycache__/test_config.cpython-312-pytest-8.3.4.pyc differ diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..3468f10 --- /dev/null +++ b/tests/test_config.py @@ -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"