use std::collections::HashSet;
use std::path::{Path, PathBuf};
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;
#[cfg(unix)]
type Identity = (u64, u64);
#[cfg(not(unix))]
type Identity = PathBuf;
#[cfg(unix)]
fn identity(p: &Path) -> Option<Identity> {
std::fs::metadata(p).ok().map(|md| (md.dev(), md.ino()))
}
#[cfg(not(unix))]
fn identity(p: &Path) -> Option<Identity> {
std::fs::canonicalize(p).ok()
}
fn push_unique(items: &mut Vec<PathBuf>, seen: &mut HashSet<Identity>, p: PathBuf) {
match identity(&p) {
Some(id) if !seen.insert(id) => {} _ => items.push(p),
}
}
pub fn resolve(paths: &[PathBuf]) -> (Vec<PathBuf>, Vec<String>) {
let mut items = Vec::new();
let mut errors = Vec::new();
let mut seen = HashSet::new();
for p in paths {
match std::fs::metadata(p) {
Err(e) => errors.push(format!("{}: {e}", p.display())),
Ok(md) if md.is_dir() => {
let mut found = walk(p, &mut errors);
found.sort();
for f in found {
push_unique(&mut items, &mut seen, f);
}
}
Ok(_) => push_unique(&mut items, &mut seen, p.clone()),
}
}
(items, errors)
}
fn walk(root: &Path, errors: &mut Vec<String>) -> Vec<PathBuf> {
let mut out = Vec::new();
for res in ignore::WalkBuilder::new(root)
.standard_filters(false)
.follow_links(true)
.build()
{
match res {
Ok(e) => {
if e.file_type().map_or(false, |t| t.is_file()) {
out.push(e.into_path());
}
}
Err(e) => errors.push(format!("{}: {e}", root.display())),
}
}
out
}
pub fn build_globs(pats: &[String]) -> anyhow::Result<Option<globset::GlobSet>> {
if pats.is_empty() {
return Ok(None);
}
let mut b = globset::GlobSetBuilder::new();
for p in pats {
b.add(globset::Glob::new(p)?);
}
Ok(Some(b.build()?))
}