use pipe_trait::Pipe;
use std::{
collections::HashSet,
fs::{canonicalize, symlink_metadata},
io,
mem::take,
path::PathBuf,
};
pub trait Api {
type Argument;
type RealPath: Eq;
type RealPathError;
fn canonicalize(path: &Self::Argument) -> Result<Self::RealPath, Self::RealPathError>;
fn is_real_dir(path: &Self::Argument) -> bool;
fn starts_with(a: &Self::RealPath, b: &Self::RealPath) -> bool;
}
pub struct RealApi;
impl Api for RealApi {
type Argument = PathBuf;
type RealPath = PathBuf;
type RealPathError = io::Error;
#[inline]
fn canonicalize(path: &Self::Argument) -> Result<Self::RealPath, Self::RealPathError> {
canonicalize(path)
}
#[inline]
fn is_real_dir(path: &Self::Argument) -> bool {
path.pipe(symlink_metadata)
.is_ok_and(|metadata| !metadata.is_symlink() && metadata.is_dir())
}
#[inline]
fn starts_with(a: &Self::RealPath, b: &Self::RealPath) -> bool {
a.starts_with(b)
}
}
pub fn remove_overlapping_paths<Api: self::Api>(arguments: &mut Vec<Api::Argument>) {
let to_remove = find_overlapping_paths_to_remove::<Api>(arguments);
remove_items_from_vec_by_indices(arguments, &to_remove);
}
pub fn find_overlapping_paths_to_remove<Api: self::Api>(
arguments: &[Api::Argument],
) -> HashSet<usize> {
let real_paths: Vec<_> = arguments
.iter()
.map(|path| {
Api::is_real_dir(path)
.then(|| Api::canonicalize(path))
.and_then(Result::ok)
})
.collect();
assert_eq!(arguments.len(), real_paths.len());
let mut to_remove = HashSet::new();
for left_index in 0..arguments.len() {
for right_index in (left_index + 1)..arguments.len() {
if let (Some(left), Some(right)) = (&real_paths[left_index], &real_paths[right_index]) {
if left == right {
to_remove.insert(right_index);
continue;
}
if Api::starts_with(left, right) {
to_remove.insert(left_index);
continue;
}
if Api::starts_with(right, left) {
to_remove.insert(right_index);
continue;
}
}
}
}
to_remove
}
pub fn remove_items_from_vec_by_indices<Item>(vec: &mut Vec<Item>, indices: &HashSet<usize>) {
if indices.is_empty() {
return;
}
if indices.len() == 1 {
let index = *indices.iter().next().unwrap();
vec.remove(index);
return;
}
*vec = vec
.pipe(take)
.into_iter()
.enumerate()
.filter(|(index, _)| !indices.contains(index))
.map(|(_, item)| item)
.collect();
}
#[cfg(test)]
mod test_remove_items_from_vec_by_indices;
#[cfg(test)]
mod test_remove_overlapping_paths;