use figment::{providers::Serialized, value, Figment, Metadata, Profile, Provider}; use serde::{Deserialize, Deserializer, Serialize}; use std::{convert::Infallible, path::PathBuf, str::FromStr}; #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] pub struct Config { pub(crate) paths: Vec, } impl Config { // Extract the configuration from any `Provider` pub fn extract(provider: T) -> figment::error::Result { Figment::from(provider).extract() } // Provide a default provider, a `Figment`. pub fn figment() -> Figment { Figment::from(Config::default()) } } impl Provider for Config { fn metadata(&self) -> Metadata { Metadata::named("Tmuxr path config") } fn data(&self) -> figment::error::Result> { Serialized::defaults(self).data() } } #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize)] pub struct PathEntry { pub path: PathBuf, pub hidden: bool, pub recurse: Option, } impl From for PathEntry { fn from(path: PathBuf) -> Self { Self { path, ..Default::default() } } } impl FromStr for PathEntry { type Err = Infallible; fn from_str(s: &str) -> Result { s.parse().map(PathBuf::into) } } impl<'de> Deserialize<'de> for PathEntry { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { #[derive(Deserialize)] #[serde(untagged)] enum Variants { String(String), Struct { path: PathBuf, #[serde(default)] hidden: bool, #[serde(default)] recurse: Option, }, } match Variants::deserialize(deserializer)? { Variants::String(s) => s.parse().map_err(serde::de::Error::custom), Variants::Struct { path, hidden, recurse, } => Ok(Self { path, hidden, recurse, }), } } } #[cfg(test)] mod tests { use super::*; use figment::providers::{Format, Serialized, Toml}; #[test] fn test_extract() { figment::Jail::expect_with(|jail| { jail.create_file( "tmuxr.toml", r#" paths = [ "/path/to/projects", { path = "/path/to/other_projects", recurse = 1, hidden = true }, { path = "/path/to/another_project", recurse = 0 }, ] "#, )?; let config: Config = Figment::from(Serialized::defaults(Config::default())) .merge(Toml::file("tmuxr.toml")) .extract()?; assert_eq!( config, Config { paths: Vec::from([ PathEntry { path: "/path/to/projects".into(), hidden: false, recurse: None, }, PathEntry { path: "/path/to/other_projects".into(), hidden: true, recurse: Some(1), }, PathEntry { path: "/path/to/another_project".into(), hidden: false, recurse: Some(0), }, ]), } ); Ok(()) }); } }