use std::collections::HashSet;
use std::fs;
use std::path::Path;
use anyhow::{Context, Result};
pub(crate) fn union_glob_with_predicate(
src_dirs: impl Iterator<Item: AsRef<Path>>,
dst_dir: &Path,
glob: &str,
predicate: impl Fn(&Path) -> bool,
) -> Result<()> {
let mut copied = HashSet::new();
for src_dir in src_dirs {
let src_dir = src_dir.as_ref();
let files = glob::glob(&format!("{}/{}", src_dir.display(), glob))
.context("Failed to read source directory")?;
for file in files {
let src = file.context("Failed to read source file")?;
if !src.is_file() {
continue;
}
let suffix = src.strip_prefix(src_dir)?;
if !predicate(suffix) {
continue;
}
let dst = dst_dir.join(suffix);
let parent_dir = dst.parent().ok_or(anyhow::anyhow!(
"Could not get parent directory of destination file"
))?;
fs::create_dir_all(parent_dir).context("Failed to create subdirectory")?;
fs::copy(&src, &dst).context("Failed to copy file")?;
let canon = dst
.canonicalize()
.context("Failed to canonicalize copied file")?;
copied.insert(canon);
}
}
let dst_files = glob::glob(&format!("{}/**/*", dst_dir.display()))
.context("Failed to read destination directory")?;
for dst_file in dst_files {
let dst = dst_file.context("Failed to read destination file")?;
let canon = dst
.canonicalize()
.context("Failed to canonicalize existing file")?;
if !copied.contains(&canon) && dst.is_file() {
fs::remove_file(canon).context("Failed to remove stale destination file")?;
}
}
Ok(())
}
pub(crate) fn union_glob(
src_dir: impl Iterator<Item: AsRef<Path>>,
dst_dir: &Path,
glob: &str,
) -> Result<()> {
union_glob_with_predicate(src_dir, dst_dir, glob, |_| true)
}