use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use walkdir::WalkDir;
use crate::matcher::{relative_path, Matcher};
#[derive(Debug, Clone)]
pub struct ProtectedPath {
pub relative: String,
pub absolute: PathBuf,
pub is_dir: bool,
pub pattern: String,
}
pub fn scan(root: &Path, matcher: &Matcher) -> Result<Vec<ProtectedPath>> {
let mut found = Vec::new();
let mut walker = WalkDir::new(root)
.min_depth(1)
.follow_links(false)
.sort_by_file_name()
.into_iter();
while let Some(entry) = walker.next() {
let entry = match entry {
Ok(entry) => entry,
Err(_) => continue,
};
let Some(relative) = relative_path(root, entry.path()) else {
continue;
};
if !matcher.is_protected(&relative) {
continue;
}
let is_dir = entry.file_type().is_dir();
found.push(ProtectedPath {
pattern: matcher
.matched_pattern(&relative)
.unwrap_or_default()
.to_string(),
relative,
absolute: entry.path().to_path_buf(),
is_dir,
});
if is_dir {
walker.skip_current_dir();
}
}
Ok(found)
}
pub fn unmatched_patterns(patterns: &[String], found: &[ProtectedPath]) -> Vec<String> {
patterns
.iter()
.filter(|pattern| !found.iter().any(|path| &&path.pattern == pattern))
.cloned()
.collect()
}
pub fn canonical_targets(found: &[ProtectedPath]) -> Result<Vec<PathBuf>> {
let mut targets = Vec::with_capacity(found.len());
for path in found {
let canonical = std::fs::canonicalize(&path.absolute)
.with_context(|| format!("failed to resolve {}", path.absolute.display()))?;
if !targets.contains(&canonical) {
targets.push(canonical);
}
}
Ok(targets)
}