diff options
Diffstat (limited to 'src/config')
| -rw-r--r-- | src/config/io.rs | 57 | ||||
| -rw-r--r-- | src/config/model.rs | 75 |
2 files changed, 112 insertions, 20 deletions
diff --git a/src/config/io.rs b/src/config/io.rs index 3bc18c4b..1a3c1920 100644 --- a/src/config/io.rs +++ b/src/config/io.rs @@ -70,19 +70,6 @@ pub(super) fn resolve_config_relative_path(path: &Path) -> PathBuf { .join(path) } -pub fn save_onboarding_choices(sound_enabled: bool, toast_enabled: bool) -> std::io::Result<()> { - let path = config_path(); - if let Some(parent) = path.parent() { - std::fs::create_dir_all(parent)?; - } - - let content = std::fs::read_to_string(&path).unwrap_or_default(); - let content = upsert_top_level_bool(&content, "onboarding", false); - let content = upsert_section_bool(&content, "ui.sound", "enabled", sound_enabled); - let content = upsert_section_bool(&content, "ui.toast", "enabled", toast_enabled); - std::fs::write(path, content) -} - pub fn config_path() -> PathBuf { if let Ok(path) = std::env::var(CONFIG_PATH_ENV_VAR) { return PathBuf::from(path); @@ -109,7 +96,7 @@ pub fn load_live_keybinds() -> Result<LiveKeybindConfig, Vec<String>> { config.live_keybinds() } -pub(super) fn upsert_top_level_bool(content: &str, key: &str, value: bool) -> String { +pub(crate) fn upsert_top_level_bool(content: &str, key: &str, value: bool) -> String { let replacement = format!("{key} = {value}"); let mut lines: Vec<String> = content.lines().map(|line| line.to_string()).collect(); let mut in_section = false; @@ -145,6 +132,38 @@ pub fn upsert_section_bool(content: &str, section: &str, key: &str, value: bool) upsert_section_raw(content, section, key, &value.to_string()) } +pub fn remove_section_key(content: &str, section: &str, key: &str) -> String { + let header = format!("[{section}]"); + let lines: Vec<&str> = content.lines().collect(); + let mut result = Vec::new(); + let mut i = 0; + let mut in_section = false; + + while i < lines.len() { + let line = lines[i]; + let trimmed = line.trim(); + + if trimmed.starts_with('[') && trimmed.ends_with(']') { + in_section = trimmed == header; + result.push(line.to_string()); + i += 1; + continue; + } + + if in_section + && (trimmed.starts_with(&format!("{key} ")) || trimmed.starts_with(&format!("{key}="))) + { + i += 1; + continue; + } + + result.push(line.to_string()); + i += 1; + } + + result.join("\n") + "\n" +} + fn upsert_section_raw(content: &str, section: &str, key: &str, value: &str) -> String { let header = format!("[{section}]"); let assignment = format!("{key} = {value}"); @@ -223,4 +242,14 @@ mod tests { assert!(updated.contains("[ui.toast]")); assert!(updated.contains("enabled = true")); } + + #[test] + fn remove_section_key_removes_matching_key_from_section() { + let content = + "[ui.toast]\nenabled = true\ndelivery = \"herdr\"\n[ui.sound]\nenabled = true\n"; + let updated = remove_section_key(content, "ui.toast", "enabled"); + assert!(!updated.contains("[ui.toast]\nenabled = true")); + assert!(updated.contains("delivery = \"herdr\"")); + assert!(updated.contains("[ui.sound]\nenabled = true")); + } } diff --git a/src/config/model.rs b/src/config/model.rs index 70eede5a..e2c39f25 100644 --- a/src/config/model.rs +++ b/src/config/model.rs @@ -1,11 +1,19 @@ -use serde::Deserialize; +use serde::{Deserialize, Deserializer, Serialize}; use super::{CommandKeybindConfig, SoundConfig, ThemeConfig, DEFAULT_SCROLLBACK_LIMIT_BYTES}; -#[derive(Debug, Clone, Deserialize)] -#[serde(default)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Default)] +#[serde(rename_all = "lowercase")] +pub enum ToastDelivery { + #[default] + Off, + Herdr, + Terminal, +} + +#[derive(Debug, Clone)] pub struct ToastConfig { - pub enabled: bool, + pub delivery: ToastDelivery, } #[derive(Debug, Default, Deserialize)] @@ -144,7 +152,31 @@ impl Default for UiConfig { impl Default for ToastConfig { fn default() -> Self { - Self { enabled: false } + Self { + delivery: ToastDelivery::Off, + } + } +} + +impl<'de> Deserialize<'de> for ToastConfig { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + #[derive(Deserialize, Default)] + #[serde(default)] + struct RawToastConfig { + delivery: Option<ToastDelivery>, + enabled: Option<bool>, + } + + let raw = RawToastConfig::deserialize(deserializer)?; + let delivery = raw.delivery.unwrap_or_else(|| match raw.enabled { + Some(true) => ToastDelivery::Herdr, + Some(false) => ToastDelivery::Off, + None => ToastDelivery::Off, + }); + Ok(Self { delivery }) } } @@ -165,10 +197,41 @@ mod tests { fn toast_config_parses() { let toml = r#" [ui.toast] +delivery = "terminal" +"#; + let config: Config = toml::from_str(toml).unwrap(); + assert_eq!(config.ui.toast.delivery, ToastDelivery::Terminal); + } + + #[test] + fn toast_config_legacy_enabled_true_maps_to_herdr() { + let toml = r#" +[ui.toast] +enabled = true +"#; + let config: Config = toml::from_str(toml).unwrap(); + assert_eq!(config.ui.toast.delivery, ToastDelivery::Herdr); + } + + #[test] + fn toast_config_legacy_enabled_false_maps_to_off() { + let toml = r#" +[ui.toast] +enabled = false +"#; + let config: Config = toml::from_str(toml).unwrap(); + assert_eq!(config.ui.toast.delivery, ToastDelivery::Off); + } + + #[test] + fn toast_config_delivery_wins_over_legacy_enabled() { + let toml = r#" +[ui.toast] enabled = true +delivery = "terminal" "#; let config: Config = toml::from_str(toml).unwrap(); - assert!(config.ui.toast.enabled); + assert_eq!(config.ui.toast.delivery, ToastDelivery::Terminal); } #[test] |
