From 1559bc15e3ce6ea59b98b4dcd34be8acdff525ce Mon Sep 17 00:00:00 2001 From: Rasmus Mecklenburg <42248344+rmburg@users.noreply.github.com> Date: Sat, 30 Nov 2024 17:49:20 +0100 Subject: [PATCH] Make twix user config keys optional (#1527) --- tools/twix/src/configuration.rs | 70 +++++++++++++++++++++++---- tools/twix/src/configuration/keys.rs | 12 +++-- tools/twix/src/configuration/merge.rs | 26 ++++++++++ 3 files changed, 94 insertions(+), 14 deletions(-) create mode 100644 tools/twix/src/configuration/merge.rs diff --git a/tools/twix/src/configuration.rs b/tools/twix/src/configuration.rs index 030d52a014..1f962a5507 100644 --- a/tools/twix/src/configuration.rs +++ b/tools/twix/src/configuration.rs @@ -1,16 +1,17 @@ +pub mod keybind_plugin; +pub mod keys; +pub mod merge; + use std::path::{Path, PathBuf}; +use merge::Merge; use serde::Deserialize; -use thiserror::Error; - -pub mod keybind_plugin; -pub mod keys; const DEFAULT_CONFIG: &str = include_str!("../config_default.toml"); -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum Error { - #[error("IO error: {0}")] + #[error("I/O error: {0}")] Io(#[from] std::io::Error), #[error("TOML parsing error: {0}")] Parsing(#[from] toml::de::Error), @@ -29,6 +30,11 @@ pub struct Configuration { pub keys: keys::Keybinds, } +#[derive(Debug, Deserialize)] +pub struct RawConfiguration { + pub keys: Option, +} + impl Configuration { pub fn load() -> Result { Self::load_from_file(config_path()) @@ -38,7 +44,7 @@ impl Configuration { match std::fs::read_to_string(&path) { Ok(config_file) => { let mut configuration = Self::default(); - let user_configuration: Configuration = toml::from_str(&config_file)?; + let user_configuration: RawConfiguration = toml::from_str(&config_file)?; configuration.merge(user_configuration); @@ -54,9 +60,19 @@ impl Configuration { } } } +} + +impl Merge for Configuration { + fn merge(&mut self, other: RawConfiguration) { + let RawConfiguration { keys } = other; + + self.keys.merge(keys); + } +} - pub fn merge(&mut self, other: Self) { - let Self { keys } = other; +impl Merge for Configuration { + fn merge(&mut self, other: Configuration) { + let Configuration { keys } = other; self.keys.merge(keys); } @@ -70,7 +86,9 @@ impl Default for Configuration { #[cfg(test)] mod tests { - use super::{Configuration, DEFAULT_CONFIG}; + use crate::configuration::merge::Merge; + + use super::{Configuration, RawConfiguration, DEFAULT_CONFIG}; #[test] fn parse_default_config() { @@ -112,4 +130,36 @@ mod tests { .unwrap() ); } + + #[test] + fn merge_partial_config() { + let mut default_config: Configuration = toml::from_str( + r#" + [keys] + C-a = "focus_left" + C-S-a = "reconnect" + "#, + ) + .unwrap(); + + let user_config: RawConfiguration = toml::from_str( + r#" + "#, + ) + .unwrap(); + + default_config.merge(user_config); + + assert_eq!( + default_config, + toml::from_str( + r#" + [keys] + C-a = "focus_left" + C-S-a = "reconnect" + "#, + ) + .unwrap() + ); + } } diff --git a/tools/twix/src/configuration/keys.rs b/tools/twix/src/configuration/keys.rs index 7d2bf6a629..fb2b667a05 100644 --- a/tools/twix/src/configuration/keys.rs +++ b/tools/twix/src/configuration/keys.rs @@ -7,6 +7,8 @@ use serde::{ }; use thiserror::Error; +use super::merge::Merge; + #[cfg_attr(test, derive(PartialEq))] #[derive(Debug, Error)] pub enum Error { @@ -144,15 +146,17 @@ impl Keybinds { } } - pub fn merge(&mut self, other: Self) { - self.keybinds.extend(other.keybinds); - } - pub fn iter(&self) -> impl Iterator { self.keybinds.iter() } } +impl Merge for Keybinds { + fn merge(&mut self, other: Self) { + self.keybinds.merge(other.keybinds); + } +} + impl<'de> Deserialize<'de> for Keybinds { fn deserialize(deserializer: D) -> Result where diff --git a/tools/twix/src/configuration/merge.rs b/tools/twix/src/configuration/merge.rs new file mode 100644 index 0000000000..49c6a31cdc --- /dev/null +++ b/tools/twix/src/configuration/merge.rs @@ -0,0 +1,26 @@ +use std::collections::HashMap; +use std::hash::Hash; + +pub trait Merge { + /// Merge `other` into `self`. + /// `self` acts as a baseline, + /// and items in `other` take precedence in the case of a conflict. + fn merge(&mut self, other: T); +} + +impl Merge for HashMap +where + K: Eq + Hash, +{ + fn merge(&mut self, other: Self) { + self.extend(other); + } +} + +impl> Merge> for T { + fn merge(&mut self, other: Option) { + if let Some(value) = other { + self.merge(value); + } + } +}