mirror of
https://github.com/bootandy/dust.git
synced 2026-06-08 11:29:05 +03:00
Fix substring bug
When one directory was a substring of another the directory would appear as a subdirectory instead of a sibling Fixed originally by this:6e03dd77e6Broken by this:db6c8a019dAdded integration test as this has bitten me before
This commit is contained in:
+4
-3
@@ -2,6 +2,7 @@
|
|||||||
extern crate clap;
|
extern crate clap;
|
||||||
|
|
||||||
use self::display::draw_it;
|
use self::display::draw_it;
|
||||||
|
use crate::utils::is_a_parent_of;
|
||||||
use clap::{App, AppSettings, Arg};
|
use clap::{App, AppSettings, Arg};
|
||||||
use utils::{find_big_ones, get_dir_tree, simplify_dir_names, sort, trim_deep_ones, Node};
|
use utils::{find_big_ones, get_dir_tree, simplify_dir_names, sort, trim_deep_ones, Node};
|
||||||
|
|
||||||
@@ -106,7 +107,6 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let tree = build_tree(biggest_ones, depth);
|
let tree = build_tree(biggest_ones, depth);
|
||||||
//println!("{:?}", tree);
|
|
||||||
|
|
||||||
draw_it(
|
draw_it(
|
||||||
permissions,
|
permissions,
|
||||||
@@ -126,7 +126,8 @@ fn build_tree(biggest_ones: Vec<(String, u64)>, depth: Option<u64>) -> Node {
|
|||||||
size: b.1,
|
size: b.1,
|
||||||
children: Vec::default(),
|
children: Vec::default(),
|
||||||
};
|
};
|
||||||
recursively_build_tree(&mut top_parent, n, depth)
|
recursively_build_tree(&mut top_parent, n, depth);
|
||||||
|
top_parent.children.sort_unstable()
|
||||||
}
|
}
|
||||||
top_parent
|
top_parent
|
||||||
}
|
}
|
||||||
@@ -140,7 +141,7 @@ fn recursively_build_tree(parent_node: &mut Node, new_node: Node, depth: Option<
|
|||||||
if let Some(c) = parent_node
|
if let Some(c) = parent_node
|
||||||
.children
|
.children
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.find(|c| new_node.name.starts_with(&c.name))
|
.find(|c| is_a_parent_of(&c.name, &new_node.name))
|
||||||
{
|
{
|
||||||
recursively_build_tree(c, new_node, new_depth);
|
recursively_build_tree(c, new_node, new_depth);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
hello
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
hello
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
hello
|
||||||
+44
-2
@@ -138,7 +138,7 @@ pub fn test_d_flag_works() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_temp_file(dir: &TempDir) -> (PathBuf) {
|
fn build_temp_file(dir: &TempDir) -> PathBuf {
|
||||||
let file_path = dir.path().join("notes.txt");
|
let file_path = dir.path().join("notes.txt");
|
||||||
let mut file = File::create(&file_path).unwrap();
|
let mut file = File::create(&file_path).unwrap();
|
||||||
writeln!(file, "I am a temp file").unwrap();
|
writeln!(file, "I am a temp file").unwrap();
|
||||||
@@ -164,7 +164,7 @@ pub fn test_soft_sym_link() {
|
|||||||
let r = soft_sym_link_output(dir_s, file_path_s, link_name_s);
|
let r = soft_sym_link_output(dir_s, file_path_s, link_name_s);
|
||||||
|
|
||||||
// We cannot guarantee which version will appear first.
|
// We cannot guarantee which version will appear first.
|
||||||
// TODO: Consider adding predictable itteration order (sort file entries by name?)
|
// TODO: Consider adding predictable iteration order (sort file entries by name?)
|
||||||
assert_cli::Assert::main_binary()
|
assert_cli::Assert::main_binary()
|
||||||
.with_args(&[dir_s])
|
.with_args(&[dir_s])
|
||||||
.stdout()
|
.stdout()
|
||||||
@@ -308,3 +308,45 @@ fn recursive_sym_link_output(dir: &str, link_name: &str) -> String {
|
|||||||
format_string(link_name, true, true, " 0B", " └──",),
|
format_string(link_name, true, true, " 0B", " └──",),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check against directories and files whos names are substrings of each other
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
pub fn test_substring_of_names() {
|
||||||
|
assert_cli::Assert::main_binary()
|
||||||
|
.with_args(&["src/test_dir2"])
|
||||||
|
.stdout()
|
||||||
|
.contains(" ─┬ test_dir2")
|
||||||
|
.stdout()
|
||||||
|
.contains(" ├─┬ dir")
|
||||||
|
.stdout()
|
||||||
|
.contains(" │ └── hello")
|
||||||
|
.stdout()
|
||||||
|
.contains(" ├── dir_name_clash")
|
||||||
|
.stdout()
|
||||||
|
.contains(" └─┬ dir_substring")
|
||||||
|
.stdout()
|
||||||
|
.contains(" └── hello")
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check against directories and files whos names are substrings of each other
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub fn test_substring_of_names() {
|
||||||
|
assert_cli::Assert::main_binary()
|
||||||
|
.with_args(&["src/test_dir2"])
|
||||||
|
.stdout()
|
||||||
|
.contains(" ─┬ test_dir2")
|
||||||
|
.stdout()
|
||||||
|
.contains(" ├─┬ dir")
|
||||||
|
.stdout()
|
||||||
|
.contains(" │ └── hello")
|
||||||
|
.stdout()
|
||||||
|
.contains(" ├─┬ dir_substring")
|
||||||
|
.stdout()
|
||||||
|
.contains(" │ └── hello")
|
||||||
|
.stdout()
|
||||||
|
.contains(" └── dir_name_clash")
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|||||||
+39
-13
@@ -7,26 +7,52 @@ use jwalk::WalkDir;
|
|||||||
mod platform;
|
mod platform;
|
||||||
use self::platform::*;
|
use self::platform::*;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, Eq)]
|
||||||
pub struct Node {
|
pub struct Node {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub size: u64,
|
pub size: u64,
|
||||||
pub children: Vec<Node>,
|
pub children: Vec<Node>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Ord for Node {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
if self.size == other.size {
|
||||||
|
self.name.cmp(&other.name)
|
||||||
|
} else {
|
||||||
|
self.size.cmp(&other.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Node {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Node {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.name == other.name && self.size == other.size && self.children == other.children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_a_parent_of(parent: &str, child: &str) -> bool {
|
||||||
|
child.starts_with(parent) && child.chars().nth(parent.chars().count()) == Some('/')
|
||||||
|
}
|
||||||
|
|
||||||
pub fn simplify_dir_names(filenames: Vec<&str>) -> HashSet<String> {
|
pub fn simplify_dir_names(filenames: Vec<&str>) -> HashSet<String> {
|
||||||
let mut top_level_names: HashSet<String> = HashSet::with_capacity(filenames.len());
|
let mut top_level_names: HashSet<String> = HashSet::with_capacity(filenames.len());
|
||||||
let mut to_remove: Vec<String> = Vec::with_capacity(filenames.len());
|
let mut to_remove: Vec<String> = Vec::with_capacity(filenames.len());
|
||||||
|
|
||||||
for t in filenames {
|
for t in filenames {
|
||||||
let top_level_name = ensure_end_slash(t);
|
let top_level_name = strip_end_slash(t);
|
||||||
let mut can_add = true;
|
let mut can_add = true;
|
||||||
|
|
||||||
for tt in top_level_names.iter() {
|
for tt in top_level_names.iter() {
|
||||||
if top_level_name.starts_with(tt) {
|
if is_a_parent_of(&top_level_name, tt) {
|
||||||
can_add = false;
|
|
||||||
} else if tt.starts_with(&top_level_name) {
|
|
||||||
to_remove.push(tt.to_string());
|
to_remove.push(tt.to_string());
|
||||||
|
} else if is_a_parent_of(tt, &top_level_name) {
|
||||||
|
can_add = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
to_remove.sort_unstable();
|
to_remove.sort_unstable();
|
||||||
@@ -62,14 +88,6 @@ pub fn get_dir_tree(
|
|||||||
(permissions == 0, data)
|
(permissions == 0, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensure_end_slash(s: &str) -> String {
|
|
||||||
let mut new_name = String::from(s);
|
|
||||||
while new_name.ends_with('/') || new_name.ends_with("/.") {
|
|
||||||
new_name.pop();
|
|
||||||
}
|
|
||||||
new_name + "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn strip_end_slash(mut new_name: &str) -> &str {
|
pub fn strip_end_slash(mut new_name: &str) -> &str {
|
||||||
while (new_name.ends_with('/') || new_name.ends_with("/.")) && new_name.len() > 1 {
|
while (new_name.ends_with('/') || new_name.ends_with("/.")) && new_name.len() > 1 {
|
||||||
new_name = &new_name[..new_name.len() - 1];
|
new_name = &new_name[..new_name.len() - 1];
|
||||||
@@ -206,4 +224,12 @@ mod tests {
|
|||||||
correct.insert("src".to_string());
|
correct.insert("src".to_string());
|
||||||
assert_eq!(simplify_dir_names(vec!["src/."]), correct);
|
assert_eq!(simplify_dir_names(vec!["src/."]), correct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simplify_dir_substring_names() {
|
||||||
|
let mut correct = HashSet::new();
|
||||||
|
correct.insert("src".to_string());
|
||||||
|
correct.insert("src_v2".to_string());
|
||||||
|
assert_eq!(simplify_dir_names(vec!["src/", "src_v2"]), correct);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user