use log::trace;
use std::collections::BTreeMap;
use std::ffi::{OsStr, OsString};
use std::fs;
use std::path::{Path, PathBuf};
const DEVNULL: &str = "/dev/null";
pub fn scan<BdS: AsRef<Path>, BdI: IntoIterator<Item = BdS>, Sp: AsRef<Path>, As: AsRef<OsStr>>(
base_dirs: BdI,
shared_path: Sp,
allowed_extensions: &[As],
ignore_dotfiles: bool,
) -> BTreeMap<OsString, PathBuf> {
let shared_path = shared_path.as_ref();
let mut files_map = BTreeMap::new();
for dir in base_dirs {
let dir = dir.as_ref().join(shared_path);
trace!("Scanning directory '{}'", dir.display());
let dir_iter = match fs::read_dir(dir) {
Ok(iter) => iter,
_ => continue,
};
for entry in dir_iter.flatten() {
let fpath = entry.path();
let fname = entry.file_name();
if ignore_dotfiles && fname.to_string_lossy().starts_with('.') {
continue;
}
if !allowed_extensions.is_empty() {
if let Some(extension) = fpath.extension() {
if !allowed_extensions.iter().any(|ae| ae.as_ref() == extension) {
continue;
}
} else {
continue;
}
}
let meta = match entry.metadata() {
Ok(m) => m,
_ => continue,
};
if !meta.file_type().is_file() {
if let Ok(target) = fs::read_link(&fpath) {
if target == Path::new(DEVNULL) {
trace!("Nulled config file '{}'", fpath.display());
files_map.remove(&fname);
}
}
continue;
}
trace!(
"Found config file '{}' at '{}'",
Path::new(&fname).display(),
fpath.display()
);
files_map.insert(fname, fpath);
}
}
files_map
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_fragments_match(
fragments: &BTreeMap<OsString, PathBuf>,
filename: &OsStr,
filepath: &Path,
) {
assert_eq!(fragments.get(filename).unwrap(), filepath);
}
fn assert_fragments_hit<T: AsRef<OsStr>>(fragments: &BTreeMap<OsString, PathBuf>, filename: T) {
assert!(fragments.get(filename.as_ref()).is_some());
}
fn assert_fragments_miss<T: AsRef<OsStr>>(
fragments: &BTreeMap<OsString, PathBuf>,
filename: T,
) {
assert!(fragments.get(filename.as_ref()).is_none());
}
#[test]
fn basic_override() {
let treedir = "tests/fixtures/tree-basic";
let dirs = [
format!("{}/{}", treedir, "usr/lib"),
format!("{}/{}", treedir, "run"),
format!("{}/{}", treedir, "etc"),
];
let expected_fragments = [
(
OsString::from("01-config-a.toml"),
Path::new(treedir).join("etc/liboverdrop.d/01-config-a.toml"),
),
(
OsString::from("02-config-b.toml"),
Path::new(treedir).join("run/liboverdrop.d/02-config-b.toml"),
),
(
OsString::from("03-config-c.toml"),
Path::new(treedir).join("etc/liboverdrop.d/03-config-c.toml"),
),
(
OsString::from("04-config-d.toml"),
Path::new(treedir).join("usr/lib/liboverdrop.d/04-config-d.toml"),
),
(
OsString::from("05-config-e.toml"),
Path::new(treedir).join("etc/liboverdrop.d/05-config-e.toml"),
),
(
OsString::from("06-config-f.toml"),
Path::new(treedir).join("run/liboverdrop.d/06-config-f.toml"),
),
(
OsString::from("07-config-g.toml"),
Path::new(treedir).join("etc/liboverdrop.d/07-config-g.toml"),
),
];
let fragments = scan(&dirs, "liboverdrop.d", &["toml"], false);
for (name, path) in &expected_fragments {
assert_fragments_match(&fragments, &name, &path);
}
let expected_keys: Vec<_> = expected_fragments
.into_iter()
.map(|(name, _)| name)
.collect();
let fragments_keys: Vec<_> = fragments.keys().cloned().collect();
assert_eq!(fragments_keys, expected_keys);
}
#[test]
fn basic_override_restrict_extensions() {
let treedir = "tests/fixtures/tree-basic";
let dirs = [format!("{}/{}", treedir, "etc")];
let fragments = scan(&dirs, "liboverdrop.d", &["toml"], false);
assert_fragments_hit(&fragments, "01-config-a.toml");
assert_fragments_miss(&fragments, "08-config-h.conf");
assert_fragments_miss(&fragments, "noextension");
}
#[test]
fn basic_override_allow_all_extensions() {
let treedir = "tests/fixtures/tree-basic";
let dirs = [format!("{}/{}", treedir, "etc")];
let fragments = scan::<_, _, _, &str>(&dirs, "liboverdrop.d", &[], false);
assert_fragments_hit(&fragments, "01-config-a.toml");
assert_fragments_hit(&fragments, "config.conf");
assert_fragments_hit(&fragments, "noextension");
}
#[test]
fn basic_override_ignore_hidden() {
let treedir = "tests/fixtures/tree-basic";
let dirs = [format!("{}/{}", treedir, "etc")];
let fragments = scan::<_, _, _, &str>(&dirs, "liboverdrop.d", &[], true);
assert_fragments_hit(&fragments, "config.conf");
assert_fragments_miss(&fragments, ".hidden.conf");
}
#[test]
fn basic_override_allow_hidden() {
let treedir = "tests/fixtures/tree-basic";
let dirs = [format!("{}/{}", treedir, "etc")];
let fragments = scan::<_, _, _, &OsStr>(&dirs, "liboverdrop.d", &[], false);
assert_fragments_hit(&fragments, "config.conf");
assert_fragments_hit(&fragments, ".hidden.conf");
}
}