diff --git a/src/lib.rs b/src/lib.rs index 146765f..75fac06 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,7 +73,8 @@ use log::trace; use std::collections::BTreeMap; use std::ffi::{OsStr, OsString}; -use std::fs; +use std::fs::{self, File}; +use std::io::BufReader; use std::path::{Path, PathBuf}; /// The well-known path to the null device used for overrides. @@ -167,8 +168,42 @@ pub fn scan, BdI: IntoIterator, Sp: AsRef, As files_map } +/// This API builds on the [`scan`] functionality, but instead of returning +/// the file paths, calls a user-provided merge function. +/// +/// At the current time, this is implemented in a simplistic way that just calls [`scan`] internally. However, +/// in the future this API can be more efficient as it isn't required to retain +/// all found file paths in memory. +pub fn scan_and_merge( + base_dirs: BdI, + shared_path: Sp, + allowed_extensions: &[As], + ignore_dotfiles: bool, + mut merge: F, +) -> Result +where + BdS: AsRef, + BdI: IntoIterator, + Sp: AsRef, + As: AsRef, + T: Default, + F: FnMut(T, &OsStr, std::io::BufReader) -> Result, + E: std::error::Error + From, +{ + let mut res = T::default(); + for (k, v) in scan(base_dirs, shared_path, allowed_extensions, ignore_dotfiles) { + let f = File::open(v).map(BufReader::new)?; + res = merge(res, &k, f)?; + } + + Ok(res) +} + #[cfg(test)] mod tests { + use std::collections::HashMap; + use std::io::BufRead; + use super::*; fn assert_fragments_match( @@ -290,4 +325,47 @@ mod tests { assert_fragments_hit(&fragments, "config.conf"); assert_fragments_hit(&fragments, ".hidden.conf"); } + + type ConfigMap = HashMap; + + // Parse a key=value line by line into a HashSet. In a real world codebase + // this would be more likely to use e.g. serde to parse a toml or yaml file + // into a final data structure. + fn merge_btreemap( + mut f: ConfigMap, + name: &OsStr, + r: BufReader, + ) -> std::io::Result { + for line in r.lines() { + let line = line?; + let (k, v) = line.trim().split_once('=').ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::Other, + format!("Invalid line {line} in file {name:?}"), + ) + })?; + f.insert(k.to_owned(), v.to_owned()); + } + Ok(f) + } + + #[test] + fn basic_merge() { + let treedir = "tests/fixtures/tree-configmerge"; + let dirs = [ + format!("{}/{}", treedir, "usr/lib"), + format!("{}/{}", treedir, "etc"), + format!("{}/{}", treedir, "run"), + ]; + + let config = + scan_and_merge(&dirs, "liboverdrop.d", &["conf"], true, merge_btreemap).unwrap(); + + assert_eq!(config.get("k1").unwrap(), "test1"); + assert_eq!(config.get("k2").unwrap(), "test2"); + assert_eq!(config.get("usrbaseconf").unwrap(), "val01"); + assert_eq!(config.get("usrbaseconf2").unwrap(), "val02"); + assert_eq!(config.get("othermasking").unwrap(), "m2"); + assert_eq!(config.len(), 6); + } } diff --git a/tests/fixtures/tree-configmerge/etc/liboverdrop.d/.hidden.conf b/tests/fixtures/tree-configmerge/etc/liboverdrop.d/.hidden.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/tree-configmerge/etc/liboverdrop.d/01-config-a.conf b/tests/fixtures/tree-configmerge/etc/liboverdrop.d/01-config-a.conf new file mode 100644 index 0000000..0a26da6 --- /dev/null +++ b/tests/fixtures/tree-configmerge/etc/liboverdrop.d/01-config-a.conf @@ -0,0 +1,2 @@ +k2=test2 +k1=test1 diff --git a/tests/fixtures/tree-configmerge/etc/liboverdrop.d/03-config-c.conf b/tests/fixtures/tree-configmerge/etc/liboverdrop.d/03-config-c.conf new file mode 100644 index 0000000..b87c3ac --- /dev/null +++ b/tests/fixtures/tree-configmerge/etc/liboverdrop.d/03-config-c.conf @@ -0,0 +1 @@ +shouldbemasked=1 diff --git a/tests/fixtures/tree-configmerge/etc/liboverdrop.d/05-other.toml b/tests/fixtures/tree-configmerge/etc/liboverdrop.d/05-other.toml new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/tree-configmerge/etc/liboverdrop.d/noextension b/tests/fixtures/tree-configmerge/etc/liboverdrop.d/noextension new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/tree-configmerge/run/liboverdrop.d/02-config-b.toml b/tests/fixtures/tree-configmerge/run/liboverdrop.d/02-config-b.toml new file mode 100644 index 0000000..9c91c28 --- /dev/null +++ b/tests/fixtures/tree-configmerge/run/liboverdrop.d/02-config-b.toml @@ -0,0 +1,2 @@ +runcfg=val01 +runcfgother=val02 diff --git a/tests/fixtures/tree-configmerge/run/liboverdrop.d/03-config-c.conf b/tests/fixtures/tree-configmerge/run/liboverdrop.d/03-config-c.conf new file mode 100644 index 0000000..9798138 --- /dev/null +++ b/tests/fixtures/tree-configmerge/run/liboverdrop.d/03-config-c.conf @@ -0,0 +1,2 @@ +maskingvalue=m1 +othermasking=m2 diff --git a/tests/fixtures/tree-configmerge/usr/lib/liboverdrop.d/01-config-a.conf b/tests/fixtures/tree-configmerge/usr/lib/liboverdrop.d/01-config-a.conf new file mode 100644 index 0000000..b0376d0 --- /dev/null +++ b/tests/fixtures/tree-configmerge/usr/lib/liboverdrop.d/01-config-a.conf @@ -0,0 +1 @@ +usrconfig=shouldbemasked diff --git a/tests/fixtures/tree-configmerge/usr/lib/liboverdrop.d/05-config-q.conf b/tests/fixtures/tree-configmerge/usr/lib/liboverdrop.d/05-config-q.conf new file mode 100644 index 0000000..e7a7ba0 --- /dev/null +++ b/tests/fixtures/tree-configmerge/usr/lib/liboverdrop.d/05-config-q.conf @@ -0,0 +1,2 @@ +usrbaseconf2=val02 +usrbaseconf=val01