use std::collections::HashSet;
use std::io::Read;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result, bail};
pub struct FileFilter {
paths: HashSet<PathBuf>,
}
impl FileFilter {
pub fn from_paths(root: &Path, paths: Vec<PathBuf>, lenient: bool) -> Result<Self> {
let canon_root = root
.canonicalize()
.with_context(|| format!("cannot resolve root path {}", root.display()))?;
let mut set = HashSet::new();
for path in paths {
match path.canonicalize() {
Ok(canon) => {
if !canon.starts_with(&canon_root) {
if lenient {
continue;
}
bail!("path {} is outside root {}", path.display(), root.display());
}
set.insert(canon);
}
Err(err) => {
if lenient {
continue;
}
return Err(err)
.with_context(|| format!("cannot resolve path {}", path.display()));
}
}
}
Ok(Self { paths: set })
}
#[must_use]
pub fn contains(&self, path: &Path) -> bool {
path.canonicalize()
.is_ok_and(|canon| self.paths.contains(&canon))
}
}
pub fn read_file_list(source: &str) -> Result<Vec<PathBuf>> {
let text = if source == "-" {
let mut buf = String::new();
std::io::stdin()
.read_to_string(&mut buf)
.context("failed to read stdin")?;
buf
} else {
std::fs::read_to_string(source)
.with_context(|| format!("failed to read file list from {source}"))?
};
Ok(text
.lines()
.filter(|line| !line.is_empty())
.map(PathBuf::from)
.collect())
}