mirror of
https://github.com/bootandy/dust.git
synced 2025-12-29 22:20:59 -08:00
- Try to use iterator adapters and collect in various places, where possible. This especially benefits draw_it. - Try to use `.map` and other similar methods on Options and Results, where possible - Replaced nearly all clones with reference-based equivalents - Summarizing nodes by file extension is now much more efficient - PartialOrd and PartialEq implementations now agree - Replace #[cfg(...)] function definitions with simpler if cfg!(...) equivelents - Simplify CLI Values handling by taking advantage of Values::default - Various spelling corrections in comments - Add `ColorState` enum to replace bool, for clarity - Fix tests that break under some detected terminal widths when paths are long - Use sort_by instead of (sort, reverse) - Use new `ExtensionNode` struct internally to simplify extension aggregation code
159 lines
5.1 KiB
Rust
159 lines
5.1 KiB
Rust
use platform::get_metadata;
|
|
use std::collections::HashSet;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use crate::platform;
|
|
use regex::Regex;
|
|
|
|
pub fn simplify_dir_names<P: AsRef<Path>>(filenames: Vec<P>) -> HashSet<PathBuf> {
|
|
let mut top_level_names: HashSet<PathBuf> = HashSet::with_capacity(filenames.len());
|
|
|
|
for t in filenames {
|
|
let top_level_name = normalize_path(t);
|
|
let mut can_add = true;
|
|
let mut to_remove: Vec<PathBuf> = Vec::new();
|
|
|
|
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;
|
|
}
|
|
}
|
|
for r in to_remove {
|
|
top_level_names.remove(&r);
|
|
}
|
|
if can_add {
|
|
top_level_names.insert(top_level_name);
|
|
}
|
|
}
|
|
|
|
top_level_names
|
|
}
|
|
|
|
pub fn get_filesystem_devices<'a, P: IntoIterator<Item = &'a PathBuf>>(paths: P) -> HashSet<u64> {
|
|
// Gets the device ids for the filesystems which are used by the argument paths
|
|
paths
|
|
.into_iter()
|
|
.filter_map(|p| match get_metadata(p, false) {
|
|
Some((_size, Some((_id, dev)))) => Some(dev),
|
|
_ => None,
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub fn normalize_path<P: AsRef<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: <https://doc.rust-lang.org/std/path/struct.Path.html#method.components>
|
|
// 4. changing to os preferred separator (automatically done by recollecting components back into a PathBuf)
|
|
path.as_ref().components().collect()
|
|
}
|
|
|
|
pub fn is_filtered_out_due_to_regex(filter_regex: &[Regex], dir: &Path) -> bool {
|
|
if filter_regex.is_empty() {
|
|
false
|
|
} else {
|
|
filter_regex
|
|
.iter()
|
|
.all(|f| !f.is_match(&dir.as_os_str().to_string_lossy()))
|
|
}
|
|
}
|
|
|
|
pub fn is_filtered_out_due_to_invert_regex(filter_regex: &[Regex], dir: &Path) -> bool {
|
|
filter_regex
|
|
.iter()
|
|
.any(|f| f.is_match(&dir.as_os_str().to_string_lossy()))
|
|
}
|
|
|
|
fn is_a_parent_of<P: AsRef<Path>>(parent: P, child: P) -> bool {
|
|
let parent = parent.as_ref();
|
|
let child = child.as_ref();
|
|
child.starts_with(parent) && !parent.starts_with(child)
|
|
}
|
|
|
|
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::<PathBuf>());
|
|
assert_eq!(simplify_dir_names(vec!["a/b/c", "a/b", "a/b/d/f"]), correct);
|
|
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::<PathBuf>());
|
|
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::<PathBuf>());
|
|
correct.insert(["a", "b"].iter().collect::<PathBuf>());
|
|
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("/", "/"));
|
|
}
|
|
}
|