feat: add configuration module with YAML loading and defaults
Made-with: Cursor
This commit is contained in:
BIN
src/cursor_flasher/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
src/cursor_flasher/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
src/cursor_flasher/__pycache__/config.cpython-312.pyc
Normal file
BIN
src/cursor_flasher/__pycache__/config.cpython-312.pyc
Normal file
Binary file not shown.
72
src/cursor_flasher/config.py
Normal file
72
src/cursor_flasher/config.py
Normal 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)
|
||||||
BIN
tests/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
tests/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
tests/__pycache__/conftest.cpython-312-pytest-8.3.4.pyc
Normal file
BIN
tests/__pycache__/conftest.cpython-312-pytest-8.3.4.pyc
Normal file
Binary file not shown.
BIN
tests/__pycache__/test_config.cpython-312-pytest-8.3.4.pyc
Normal file
BIN
tests/__pycache__/test_config.cpython-312-pytest-8.3.4.pyc
Normal file
Binary file not shown.
56
tests/test_config.py
Normal file
56
tests/test_config.py
Normal 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"
|
||||||
Reference in New Issue
Block a user