use platform::get_metadata; use std::collections::HashSet; use std::path::{Path, PathBuf}; use crate::platform; fn is_a_parent_of>(parent: P, child: P) -> bool { let parent = parent.as_ref(); let child = child.as_ref(); child.starts_with(parent) && !parent.starts_with(child) } pub fn simplify_dir_names>(filenames: Vec

) -> HashSet { let mut top_level_names: HashSet = HashSet::with_capacity(filenames.len()); let mut to_remove: Vec = Vec::with_capacity(filenames.len()); for t in filenames { let top_level_name = normalize_path(t); let mut can_add = true; for tt in top_level_names.iter() { if is_a_parent_of(&top_level_name, tt) { to_remove.push(tt.to_path_buf()); } else if is_a_parent_of(tt, &top_level_name) { can_add = false; } } to_remove.sort_unstable(); top_level_names.retain(|tr| to_remove.binary_search(tr).is_err()); to_remove.clear(); if can_add { top_level_names.insert(top_level_name); } } top_level_names } pub fn get_filesystem_devices<'a, P: IntoIterator>(paths: P) -> HashSet { // Gets the device ids for the filesystems which are used by the argument paths paths .into_iter() .filter_map(|p| { let meta = get_metadata(&p, false); if let Some((_size, Some((_id, dev)))) = meta { Some(dev) } else { None } }) .collect() } pub fn normalize_path>(path: P) -> PathBuf { // normalize path ... // 1. removing repeated separators // 2. removing interior '.' ("current directory") path segments // 3. removing trailing extra separators and '.' ("current directory") path segments // * `Path.components()` does all the above work; ref: // 4. changing to os preferred separator (automatically done by recollecting components back into a PathBuf) path.as_ref().components().collect::() } mod tests { #[allow(unused_imports)] use super::*; #[test] fn test_simplify_dir() { let mut correct = HashSet::new(); correct.insert(PathBuf::from("a")); assert_eq!(simplify_dir_names(vec!["a"]), correct); } #[test] fn test_simplify_dir_rm_subdir() { let mut correct = HashSet::new(); correct.insert(["a", "b"].iter().collect::()); assert_eq!(simplify_dir_names(vec!["a/b", "a/b/c", "a/b/d/f"]), correct); } #[test] fn test_simplify_dir_duplicates() { let mut correct = HashSet::new(); correct.insert(["a", "b"].iter().collect::()); correct.insert(PathBuf::from("c")); assert_eq!( simplify_dir_names(vec![ "a/b", "a/b//", "a/././b///", "c", "c/", "c/.", "c/././", "c/././." ]), correct ); } #[test] fn test_simplify_dir_rm_subdir_and_not_substrings() { let mut correct = HashSet::new(); correct.insert(PathBuf::from("b")); correct.insert(["c", "a", "b"].iter().collect::()); correct.insert(["a", "b"].iter().collect::()); assert_eq!(simplify_dir_names(vec!["a/b", "c/a/b/", "b"]), correct); } #[test] fn test_simplify_dir_dots() { let mut correct = HashSet::new(); correct.insert(PathBuf::from("src")); assert_eq!(simplify_dir_names(vec!["src/."]), correct); } #[test] fn test_simplify_dir_substring_names() { let mut correct = HashSet::new(); correct.insert(PathBuf::from("src")); correct.insert(PathBuf::from("src_v2")); assert_eq!(simplify_dir_names(vec!["src/", "src_v2"]), correct); } #[test] fn test_is_a_parent_of() { assert!(is_a_parent_of("/usr", "/usr/andy")); assert!(is_a_parent_of("/usr", "/usr/andy/i/am/descendant")); assert!(!is_a_parent_of("/usr", "/usr/.")); assert!(!is_a_parent_of("/usr", "/usr/")); assert!(!is_a_parent_of("/usr", "/usr")); assert!(!is_a_parent_of("/usr/", "/usr")); assert!(!is_a_parent_of("/usr/andy", "/usr")); assert!(!is_a_parent_of("/usr/andy", "/usr/sibling")); assert!(!is_a_parent_of("/usr/folder", "/usr/folder_not_a_child")); } #[test] fn test_is_a_parent_of_root() { assert!(is_a_parent_of("/", "/usr/andy")); assert!(is_a_parent_of("/", "/usr")); assert!(!is_a_parent_of("/", "/")); } }