From b7271b1da2f53eb5b71e29950941bfcf31843c8a Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Tue, 20 Mar 2018 22:09:29 -0700 Subject: [PATCH 1/4] start of refactor --- Cargo.toml | 6 +- src/lib.rs | 332 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 353 +--------------------------------------------------- 3 files changed, 339 insertions(+), 352 deletions(-) create mode 100644 src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index d175e89..b305006 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "dust" -version = "0.1.0" -authors = ["bootandy "] +version = "0.1.1" +authors = ["bootandy ", "nebkor "] [dependencies] -ansi_term = "0.11" +ansi_term = "*" clap = "2.31" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..039ca79 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,332 @@ +use std::collections::HashSet; + +use std::cmp::{self, Ordering}; +use std::fs::{self, Metadata, ReadDir}; +use std::io; + +#[derive(Clone, Debug)] +struct Node { + dir: DirEnt, + children: Vec, +} +impl Ord for Node { + fn cmp(&self, other: &Self) -> Ordering { + if self.dir.size > other.dir.size { + Ordering::Less + } else if self.dir.size < other.dir.size { + Ordering::Greater + } else { + let my_slashes = self.dir.name.matches('/').count(); + let other_slashes = other.dir.name.matches('/').count(); + + if my_slashes > other_slashes { + Ordering::Greater + } else if my_slashes < other_slashes { + Ordering::Less + } else { + if self.dir.name < other.dir.name { + Ordering::Less + } else if self.dir.name > other.dir.name { + Ordering::Greater + } else { + Ordering::Equal + } + } + } + } +} +impl PartialOrd for Node { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl PartialEq for Node { + fn eq(&self, other: &Self) -> bool { + (&self.dir.name, self.dir.size) == (&other.dir.name, other.dir.size) + } +} +impl Eq for Node {} + +#[derive(Clone, Debug)] +struct DirEnt { + name: String, + size: u64, +} + +pub fn get_dir_tree(filenames: &Vec<&str>) -> (bool, Vec) { + let mut permissions = true; + let mut results = vec![]; + for b in filenames { + let mut new_name = String::from(*b); + while new_name.chars().last() == Some('/') && new_name.len() != 1 { + new_name.pop(); + } + let (hp, data) = examine_dir_str(new_name); + permissions = permissions && hp; + results.push(data); + } + (permissions, results) +} + +fn examine_dir_str(loc: String) -> (bool, Node) { + let mut inodes: HashSet = HashSet::new(); + let (hp, result) = examine_dir(fs::read_dir(&loc), &mut inodes); + + // This needs to be folded into the below recursive call somehow + let new_size = result.iter().fold(0, |a, b| a + b.dir.size); + ( + hp, + Node { + dir: DirEnt { + name: loc, + size: new_size, + }, + children: result, + }, + ) +} + +#[cfg(target_os = "linux")] +fn get_metadata_blocks_and_inode(d: &std::fs::DirEntry) -> Option<(u64, u64)> { + use std::os::linux::fs::MetadataExt; + match d.metadata().ok() { + Some(md) => Some((md.len(), md.st_ino())), + None => None, + } +} + +#[cfg(target_os = "unix")] +fn get_metadata_blocks_and_inode(d: &std::fs::DirEntry) -> Option<(u64, u64)> { + use std::os::unix::fs::MetadataExt; + match d.metadata().ok() { + Some(md) => Some((md.len(), md.ino())), + None => None, + } +} + +#[cfg(target_os = "macos")] +fn get_metadata_blocks_and_inode(d: &std::fs::DirEntry) -> Option<(u64, u64)> { + use std::os::macos::fs::MetadataExt; + match d.metadata().ok() { + Some(md) => Some((md.len(), md.st_ino())), + None => None, + } +} + +#[cfg(not(any(target_os = "linux", target_os = "unix", target_os = "macos")))] +fn get_metadata_blocks_and_inode(_d: &std::fs::DirEntry) -> Option<(u64, u64)> { + match _d.metadata().ok() { + Some(md) => Some((md.len(), 0)), //move to option not 0 + None => None, + } +} + +fn examine_dir(a_dir: io::Result, inodes: &mut HashSet) -> (bool, Vec) { + let mut result = vec![]; + let mut have_permission = true; + + if a_dir.is_ok() { + let paths = a_dir.unwrap(); + for dd in paths { + match dd { + Ok(d) => { + let file_type = d.file_type().ok(); + let maybe_size_and_inode = get_metadata_blocks_and_inode(&d); + + match (file_type, maybe_size_and_inode) { + (Some(file_type), Some((size, inode))) => { + let s = d.path().to_string_lossy().to_string(); + if inodes.contains(&inode) { + continue; + } + inodes.insert(inode); + + if d.path().is_dir() && !file_type.is_symlink() { + let (hp, recursive) = examine_dir(fs::read_dir(d.path()), inodes); + have_permission = have_permission && hp; + let new_size = recursive.iter().fold(size, |a, b| a + b.dir.size); + result.push(Node { + dir: DirEnt { + name: s, + size: new_size, + }, + children: recursive, + }) + } else { + result.push(Node { + dir: DirEnt { + name: s, + size: size, + }, + children: vec![], + }) + } + } + (_, None) => have_permission = false, + (_, _) => (), + } + } + Err(_) => (), + } + } + } else { + have_permission = false; + } + (have_permission, result) +} + +// We start with a list of root directories - these must be the biggest folders +// We then repeadedly merge in the children of the biggest directory - Each iteration +// the next biggest directory's children are merged in. +pub fn find_big_ones<'a>(l: &'a Vec, max_to_show: usize) -> Vec<&Node> { + let mut new_l: Vec<&Node> = l.iter().map(|a| a).collect(); + new_l.sort(); + + for processed_pointer in 0..max_to_show { + if new_l.len() == processed_pointer { + break; + } + // Must be a list of pointers into new_l otherwise b_list will go out of scope + // when it is deallocated + let mut b_list: Vec<&Node> = new_l[processed_pointer] + .children + .iter() + .map(|a| a) + .collect(); + new_l.extend(b_list); + new_l.sort(); + } + + if new_l.len() > max_to_show { + new_l[0..max_to_show + 1].to_vec() + } else { + new_l + } +} + +pub fn display(permissions: bool, to_display: &Vec<&Node>) -> () { + if !permissions { + eprintln!("Did not have permissions for all directories"); + } + + display_node(to_display[0], &to_display, true, 1, "") +} + +fn display_node>( + node_to_print: &Node, + to_display: &Vec<&Node>, + is_first: bool, + depth: u8, + indentation_str: S, +) { + let mut is = indentation_str.into(); + print_this_node(node_to_print, is_first, depth, is.as_ref()); + + is = is.replace("└─┬", " "); + is = is.replace("└──", " "); + is = is.replace("├──", "│ "); + is = is.replace("├─┬", "│ "); + + let printable_node_slashes = node_to_print.dir.name.matches('/').count(); + + let mut num_siblings = to_display.iter().fold(0, |a, b| { + if node_to_print.children.contains(b) + && b.dir.name.matches('/').count() == printable_node_slashes + 1 + { + a + 1 + } else { + a + } + }); + + let mut is_biggest = true; + let mut has_display_children = false; + for node in to_display { + if node_to_print.children.contains(node) { + let has_children = node.children.len() > 0; + if node.dir.name.matches("/").count() == printable_node_slashes + 1 { + num_siblings -= 1; + for ref n in node.children.iter() { + has_display_children = has_display_children || to_display.contains(n); + } + let has_children = has_children && has_display_children; + let tree_chars = { + if num_siblings == 0 { + if has_children { + "└─┬" + } else { + "└──" + } + } else { + if has_children { + "├─┬" + } else { + "├──" + } + } + }; + display_node( + &node, + to_display, + is_biggest, + depth + 1, + is.to_string() + tree_chars, + ); + is_biggest = false; + } + } + } +} + +fn print_this_node(node_to_print: &Node, is_biggest: bool, depth: u8, indentation_str: &str) { + let padded_size = format!("{:>5}", human_readable_number(node_to_print.dir.size),); + println!( + "{} {} {}", + if is_biggest { + Fixed(196).paint(padded_size) + } else { + Fixed(7).paint(padded_size) + }, + indentation_str, + Fixed(7) + .on(Fixed(cmp::min(8, (depth) as u8) + 231)) + .paint(node_to_print.dir.name.to_string()) + ); +} + +fn human_readable_number(size: u64) -> (String) { + let units = vec!["T", "G", "M", "K"]; //make static + + //return format!("{}B", size); + + for (i, u) in units.iter().enumerate() { + let marker = 1024u64.pow((units.len() - i) as u32); + if size >= marker { + if size / marker < 10 { + return format!("{:.1}{}", (size as f32 / marker as f32), u); + } else { + return format!("{}{}", (size / marker), u); + } + } + } + return format!("{}B", size); +} + +mod tests { + #[allow(unused_imports)] + use super::*; + + #[test] + fn test_human_readable_number() { + assert_eq!(human_readable_number(1), "1B"); + assert_eq!(human_readable_number(956), "956B"); + assert_eq!(human_readable_number(1004), "1004B"); + assert_eq!(human_readable_number(1024), "1.0K"); + assert_eq!(human_readable_number(1536), "1.5K"); + assert_eq!(human_readable_number(1024 * 512), "512K"); + assert_eq!(human_readable_number(1024 * 1024), "1.0M"); + assert_eq!(human_readable_number(1024 * 1024 * 1024 - 1), "1023M"); + assert_eq!(human_readable_number(1024 * 1024 * 1024 * 20), "20G"); + assert_eq!(human_readable_number(1024 * 1024 * 1024 * 1024), "1.0T"); + } +} diff --git a/src/main.rs b/src/main.rs index 4630eac..4ff2f01 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,73 +1,12 @@ -// test: -// recursive dirs that link to each other. -// Pass in bad dir name -// num to search for is less than num available -// admin files. -// +extern crate dust; +use dust::*; + extern crate ansi_term; +use ansi_term::Colour::Fixed; #[macro_use] extern crate clap; - -use std::collections::HashSet; - -use ansi_term::Colour::Fixed; use clap::{App, AppSettings, Arg}; -use std::cmp; -use std::cmp::Ordering; -use std::fs; -use std::fs::ReadDir; -use std::io; - -#[derive(Clone, Debug)] -struct Node { - dir: Dir, - children: Vec, -} -impl Ord for Node { - fn cmp(&self, other: &Self) -> Ordering { - if self.dir.size > other.dir.size { - Ordering::Less - } else if self.dir.size < other.dir.size { - Ordering::Greater - } else { - let my_slashes = self.dir.name.matches('/').count(); - let other_slashes = other.dir.name.matches('/').count(); - - if my_slashes > other_slashes { - Ordering::Greater - } else if my_slashes < other_slashes { - Ordering::Less - } else { - if self.dir.name < other.dir.name { - Ordering::Less - } else if self.dir.name > other.dir.name { - Ordering::Greater - } else { - Ordering::Equal - } - } - } - } -} -impl PartialOrd for Node { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} -impl PartialEq for Node { - fn eq(&self, other: &Self) -> bool { - (&self.dir.name, self.dir.size) == (&other.dir.name, other.dir.size) - } -} -impl Eq for Node {} - -#[derive(Clone, Debug)] -struct Dir { - name: String, - size: u64, -} - static DEFAULT_NUMBER_OF_LINES: &'static str = "15"; fn main() { @@ -95,287 +34,3 @@ fn main() { let slice_it = find_big_ones(&results, number_of_lines); display(permissions, &slice_it); } - -fn get_dir_tree(filenames: &Vec<&str>) -> (bool, Vec) { - let mut permissions = true; - let mut results = vec![]; - for b in filenames { - let mut new_name = String::from(*b); - while new_name.chars().last() == Some('/') && new_name.len() != 1 { - new_name.pop(); - } - let (hp, data) = examine_dir_str(new_name); - permissions = permissions && hp; - results.push(data); - } - (permissions, results) -} - -fn examine_dir_str(loc: String) -> (bool, Node) { - let mut inodes: HashSet = HashSet::new(); - let (hp, result) = examine_dir(fs::read_dir(&loc), &mut inodes); - - // This needs to be folded into the below recursive call somehow - let new_size = result.iter().fold(0, |a, b| a + b.dir.size); - ( - hp, - Node { - dir: Dir { - name: loc, - size: new_size, - }, - children: result, - }, - ) -} - -#[cfg(target_os = "linux")] -fn get_metadata_blocks_and_inode(d: &std::fs::DirEntry) -> Option<(u64, u64)> { - use std::os::linux::fs::MetadataExt; - match d.metadata().ok() { - Some(md) => Some((md.len(), md.st_ino())), - None => None, - } -} - -#[cfg(target_os = "unix")] -fn get_metadata_blocks_and_inode(d: &std::fs::DirEntry) -> Option<(u64, u64)> { - use std::os::unix::fs::MetadataExt; - match d.metadata().ok() { - Some(md) => Some((md.len(), md.ino())), - None => None, - } -} - -#[cfg(target_os = "macos")] -fn get_metadata_blocks_and_inode(d: &std::fs::DirEntry) -> Option<(u64, u64)> { - use std::os::macos::fs::MetadataExt; - match d.metadata().ok() { - Some(md) => Some((md.len(), md.st_ino())), - None => None, - } -} - -#[cfg(not(any(target_os = "linux", target_os = "unix", target_os = "macos")))] -fn get_metadata_blocks_and_inode(_d: &std::fs::DirEntry) -> Option<(u64, u64)> { - match _d.metadata().ok() { - Some(md) => Some((md.len(), 0)), //move to option not 0 - None => None, - } -} - -fn examine_dir(a_dir: io::Result, inodes: &mut HashSet) -> (bool, Vec) { - let mut result = vec![]; - let mut have_permission = true; - - if a_dir.is_ok() { - let paths = a_dir.unwrap(); - for dd in paths { - match dd { - Ok(d) => { - let file_type = d.file_type().ok(); - let maybe_size_and_inode = get_metadata_blocks_and_inode(&d); - - match (file_type, maybe_size_and_inode) { - (Some(file_type), Some((size, inode))) => { - let s = d.path().to_string_lossy().to_string(); - if inodes.contains(&inode) { - continue; - } - inodes.insert(inode); - - if d.path().is_dir() && !file_type.is_symlink() { - let (hp, recursive) = examine_dir(fs::read_dir(d.path()), inodes); - have_permission = have_permission && hp; - let new_size = recursive.iter().fold(size, |a, b| a + b.dir.size); - result.push(Node { - dir: Dir { - name: s, - size: new_size, - }, - children: recursive, - }) - } else { - result.push(Node { - dir: Dir { - name: s, - size: size, - }, - children: vec![], - }) - } - } - (_, None) => have_permission = false, - (_, _) => (), - } - } - Err(_) => (), - } - } - } else { - have_permission = false; - } - (have_permission, result) -} - -// We start with a list of root directories - these must be the biggest folders -// We then repeadedly merge in the children of the biggest directory - Each iteration -// the next biggest directory's children are merged in. -fn find_big_ones<'a>(l: &'a Vec, max_to_show: usize) -> Vec<&Node> { - let mut new_l: Vec<&Node> = l.iter().map(|a| a).collect(); - new_l.sort(); - - for processed_pointer in 0..max_to_show { - if new_l.len() == processed_pointer { - break; - } - // Must be a list of pointers into new_l otherwise b_list will go out of scope - // when it is deallocated - let mut b_list: Vec<&Node> = new_l[processed_pointer] - .children - .iter() - .map(|a| a) - .collect(); - new_l.extend(b_list); - new_l.sort(); - /*println!( - "{:?} -------------------", - new_l - .iter() - .map(|a| a.dir.size.to_string() + ": " + &a.dir.name) - .collect::>() - );*/ - } - if new_l.len() > max_to_show { - new_l[0..max_to_show + 1].to_vec() - } else { - new_l - } -} - -fn display(permissions: bool, to_display: &Vec<&Node>) -> () { - if !permissions { - eprintln!("Did not have permissions for all directories"); - } - - display_node(to_display[0], &to_display, true, 1, "") -} - -fn display_node>( - node_to_print: &Node, - to_display: &Vec<&Node>, - is_first: bool, - depth: u8, - indentation_str: S, -) { - let mut is = indentation_str.into(); - print_this_node(node_to_print, is_first, depth, is.as_ref()); - - is = is.replace("└─┬", " "); - is = is.replace("└──", " "); - is = is.replace("├──", "│ "); - is = is.replace("├─┬", "│ "); - - let printable_node_slashes = node_to_print.dir.name.matches('/').count(); - - let mut num_siblings = to_display.iter().fold(0, |a, b| { - if node_to_print.children.contains(b) - && b.dir.name.matches('/').count() == printable_node_slashes + 1 - { - a + 1 - } else { - a - } - }); - - let mut is_biggest = true; - let mut has_display_children = false; - for node in to_display { - if node_to_print.children.contains(node) { - let has_children = node.children.len() > 0; - if node.dir.name.matches("/").count() == printable_node_slashes + 1 { - num_siblings -= 1; - for ref n in node.children.iter() { - has_display_children = has_display_children || to_display.contains(n); - } - let has_children = has_children && has_display_children; - let tree_chars = { - if num_siblings == 0 { - if has_children { - "└─┬" - } else { - "└──" - } - } else { - if has_children { - "├─┬" - } else { - "├──" - } - } - }; - display_node( - &node, - to_display, - is_biggest, - depth + 1, - is.to_string() + tree_chars, - ); - is_biggest = false; - } - } - } -} - -fn print_this_node(node_to_print: &Node, is_biggest: bool, depth: u8, indentation_str: &str) { - let padded_size = format!("{:>5}", human_readable_number(node_to_print.dir.size),); - println!( - "{} {} {}", - if is_biggest { - Fixed(196).paint(padded_size) - } else { - Fixed(7).paint(padded_size) - }, - indentation_str, - Fixed(7) - .on(Fixed(cmp::min(8, (depth) as u8) + 231)) - .paint(node_to_print.dir.name.to_string()) - ); -} - -fn human_readable_number(size: u64) -> (String) { - let units = vec!["T", "G", "M", "K"]; //make static - - //return format!("{}B", size); - - for (i, u) in units.iter().enumerate() { - let marker = 1024u64.pow((units.len() - i) as u32); - if size >= marker { - if size / marker < 10 { - return format!("{:.1}{}", (size as f32 / marker as f32), u); - } else { - return format!("{}{}", (size / marker), u); - } - } - } - return format!("{}B", size); -} - -mod tests { - #[allow(unused_imports)] - use super::*; - - #[test] - fn test_human_readable_number() { - assert_eq!(human_readable_number(1), "1B"); - assert_eq!(human_readable_number(956), "956B"); - assert_eq!(human_readable_number(1004), "1004B"); - assert_eq!(human_readable_number(1024), "1.0K"); - assert_eq!(human_readable_number(1536), "1.5K"); - assert_eq!(human_readable_number(1024 * 512), "512K"); - assert_eq!(human_readable_number(1024 * 1024), "1.0M"); - assert_eq!(human_readable_number(1024 * 1024 * 1024 - 1), "1023M"); - assert_eq!(human_readable_number(1024 * 1024 * 1024 * 20), "20G"); - assert_eq!(human_readable_number(1024 * 1024 * 1024 * 1024), "1.0T"); - } -} From 0faf7952846abd4fdc959d5094f16dc36b864a08 Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Tue, 20 Mar 2018 22:40:53 -0700 Subject: [PATCH 2/4] First step of refactor done. --- Cargo.toml | 2 +- src/lib.rs | 303 ++------------------------------------------------- src/main.rs | 6 +- src/utils.rs | 289 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 304 insertions(+), 296 deletions(-) create mode 100644 src/utils.rs diff --git a/Cargo.toml b/Cargo.toml index b305006..588df2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,5 +4,5 @@ version = "0.1.1" authors = ["bootandy ", "nebkor "] [dependencies] -ansi_term = "*" +ansi_term = "0.11" clap = "2.31" diff --git a/src/lib.rs b/src/lib.rs index 039ca79..5deb080 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,17 @@ -use std::collections::HashSet; - -use std::cmp::{self, Ordering}; -use std::fs::{self, Metadata, ReadDir}; -use std::io; +use std::cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd}; #[derive(Clone, Debug)] -struct Node { - dir: DirEnt, - children: Vec, +pub struct Node { + pub dir: DirEnt, + pub children: Vec, } + +#[derive(Clone, Debug)] +pub struct DirEnt { + pub name: String, + pub size: u64, +} + impl Ord for Node { fn cmp(&self, other: &Self) -> Ordering { if self.dir.size > other.dir.size { @@ -46,287 +49,3 @@ impl PartialEq for Node { } } impl Eq for Node {} - -#[derive(Clone, Debug)] -struct DirEnt { - name: String, - size: u64, -} - -pub fn get_dir_tree(filenames: &Vec<&str>) -> (bool, Vec) { - let mut permissions = true; - let mut results = vec![]; - for b in filenames { - let mut new_name = String::from(*b); - while new_name.chars().last() == Some('/') && new_name.len() != 1 { - new_name.pop(); - } - let (hp, data) = examine_dir_str(new_name); - permissions = permissions && hp; - results.push(data); - } - (permissions, results) -} - -fn examine_dir_str(loc: String) -> (bool, Node) { - let mut inodes: HashSet = HashSet::new(); - let (hp, result) = examine_dir(fs::read_dir(&loc), &mut inodes); - - // This needs to be folded into the below recursive call somehow - let new_size = result.iter().fold(0, |a, b| a + b.dir.size); - ( - hp, - Node { - dir: DirEnt { - name: loc, - size: new_size, - }, - children: result, - }, - ) -} - -#[cfg(target_os = "linux")] -fn get_metadata_blocks_and_inode(d: &std::fs::DirEntry) -> Option<(u64, u64)> { - use std::os::linux::fs::MetadataExt; - match d.metadata().ok() { - Some(md) => Some((md.len(), md.st_ino())), - None => None, - } -} - -#[cfg(target_os = "unix")] -fn get_metadata_blocks_and_inode(d: &std::fs::DirEntry) -> Option<(u64, u64)> { - use std::os::unix::fs::MetadataExt; - match d.metadata().ok() { - Some(md) => Some((md.len(), md.ino())), - None => None, - } -} - -#[cfg(target_os = "macos")] -fn get_metadata_blocks_and_inode(d: &std::fs::DirEntry) -> Option<(u64, u64)> { - use std::os::macos::fs::MetadataExt; - match d.metadata().ok() { - Some(md) => Some((md.len(), md.st_ino())), - None => None, - } -} - -#[cfg(not(any(target_os = "linux", target_os = "unix", target_os = "macos")))] -fn get_metadata_blocks_and_inode(_d: &std::fs::DirEntry) -> Option<(u64, u64)> { - match _d.metadata().ok() { - Some(md) => Some((md.len(), 0)), //move to option not 0 - None => None, - } -} - -fn examine_dir(a_dir: io::Result, inodes: &mut HashSet) -> (bool, Vec) { - let mut result = vec![]; - let mut have_permission = true; - - if a_dir.is_ok() { - let paths = a_dir.unwrap(); - for dd in paths { - match dd { - Ok(d) => { - let file_type = d.file_type().ok(); - let maybe_size_and_inode = get_metadata_blocks_and_inode(&d); - - match (file_type, maybe_size_and_inode) { - (Some(file_type), Some((size, inode))) => { - let s = d.path().to_string_lossy().to_string(); - if inodes.contains(&inode) { - continue; - } - inodes.insert(inode); - - if d.path().is_dir() && !file_type.is_symlink() { - let (hp, recursive) = examine_dir(fs::read_dir(d.path()), inodes); - have_permission = have_permission && hp; - let new_size = recursive.iter().fold(size, |a, b| a + b.dir.size); - result.push(Node { - dir: DirEnt { - name: s, - size: new_size, - }, - children: recursive, - }) - } else { - result.push(Node { - dir: DirEnt { - name: s, - size: size, - }, - children: vec![], - }) - } - } - (_, None) => have_permission = false, - (_, _) => (), - } - } - Err(_) => (), - } - } - } else { - have_permission = false; - } - (have_permission, result) -} - -// We start with a list of root directories - these must be the biggest folders -// We then repeadedly merge in the children of the biggest directory - Each iteration -// the next biggest directory's children are merged in. -pub fn find_big_ones<'a>(l: &'a Vec, max_to_show: usize) -> Vec<&Node> { - let mut new_l: Vec<&Node> = l.iter().map(|a| a).collect(); - new_l.sort(); - - for processed_pointer in 0..max_to_show { - if new_l.len() == processed_pointer { - break; - } - // Must be a list of pointers into new_l otherwise b_list will go out of scope - // when it is deallocated - let mut b_list: Vec<&Node> = new_l[processed_pointer] - .children - .iter() - .map(|a| a) - .collect(); - new_l.extend(b_list); - new_l.sort(); - } - - if new_l.len() > max_to_show { - new_l[0..max_to_show + 1].to_vec() - } else { - new_l - } -} - -pub fn display(permissions: bool, to_display: &Vec<&Node>) -> () { - if !permissions { - eprintln!("Did not have permissions for all directories"); - } - - display_node(to_display[0], &to_display, true, 1, "") -} - -fn display_node>( - node_to_print: &Node, - to_display: &Vec<&Node>, - is_first: bool, - depth: u8, - indentation_str: S, -) { - let mut is = indentation_str.into(); - print_this_node(node_to_print, is_first, depth, is.as_ref()); - - is = is.replace("└─┬", " "); - is = is.replace("└──", " "); - is = is.replace("├──", "│ "); - is = is.replace("├─┬", "│ "); - - let printable_node_slashes = node_to_print.dir.name.matches('/').count(); - - let mut num_siblings = to_display.iter().fold(0, |a, b| { - if node_to_print.children.contains(b) - && b.dir.name.matches('/').count() == printable_node_slashes + 1 - { - a + 1 - } else { - a - } - }); - - let mut is_biggest = true; - let mut has_display_children = false; - for node in to_display { - if node_to_print.children.contains(node) { - let has_children = node.children.len() > 0; - if node.dir.name.matches("/").count() == printable_node_slashes + 1 { - num_siblings -= 1; - for ref n in node.children.iter() { - has_display_children = has_display_children || to_display.contains(n); - } - let has_children = has_children && has_display_children; - let tree_chars = { - if num_siblings == 0 { - if has_children { - "└─┬" - } else { - "└──" - } - } else { - if has_children { - "├─┬" - } else { - "├──" - } - } - }; - display_node( - &node, - to_display, - is_biggest, - depth + 1, - is.to_string() + tree_chars, - ); - is_biggest = false; - } - } - } -} - -fn print_this_node(node_to_print: &Node, is_biggest: bool, depth: u8, indentation_str: &str) { - let padded_size = format!("{:>5}", human_readable_number(node_to_print.dir.size),); - println!( - "{} {} {}", - if is_biggest { - Fixed(196).paint(padded_size) - } else { - Fixed(7).paint(padded_size) - }, - indentation_str, - Fixed(7) - .on(Fixed(cmp::min(8, (depth) as u8) + 231)) - .paint(node_to_print.dir.name.to_string()) - ); -} - -fn human_readable_number(size: u64) -> (String) { - let units = vec!["T", "G", "M", "K"]; //make static - - //return format!("{}B", size); - - for (i, u) in units.iter().enumerate() { - let marker = 1024u64.pow((units.len() - i) as u32); - if size >= marker { - if size / marker < 10 { - return format!("{:.1}{}", (size as f32 / marker as f32), u); - } else { - return format!("{}{}", (size / marker), u); - } - } - } - return format!("{}B", size); -} - -mod tests { - #[allow(unused_imports)] - use super::*; - - #[test] - fn test_human_readable_number() { - assert_eq!(human_readable_number(1), "1B"); - assert_eq!(human_readable_number(956), "956B"); - assert_eq!(human_readable_number(1004), "1004B"); - assert_eq!(human_readable_number(1024), "1.0K"); - assert_eq!(human_readable_number(1536), "1.5K"); - assert_eq!(human_readable_number(1024 * 512), "512K"); - assert_eq!(human_readable_number(1024 * 1024), "1.0M"); - assert_eq!(human_readable_number(1024 * 1024 * 1024 - 1), "1023M"); - assert_eq!(human_readable_number(1024 * 1024 * 1024 * 20), "20G"); - assert_eq!(human_readable_number(1024 * 1024 * 1024 * 1024), "1.0T"); - } -} diff --git a/src/main.rs b/src/main.rs index 4ff2f01..66c4592 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ extern crate dust; -use dust::*; -extern crate ansi_term; -use ansi_term::Colour::Fixed; +mod utils; +use utils::{display, find_big_ones, get_dir_tree}; + #[macro_use] extern crate clap; use clap::{App, AppSettings, Arg}; diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..fa16ce1 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,289 @@ +use std::collections::HashSet; +use std; +use std::fs::{self, ReadDir}; +use std::io; + +use std::cmp; + +use dust::{DirEnt, Node}; + +extern crate ansi_term; +use self::ansi_term::Colour::Fixed; + +pub fn get_dir_tree(filenames: &Vec<&str>) -> (bool, Vec) { + let mut permissions = true; + let mut results = vec![]; + for b in filenames { + let mut new_name = String::from(*b); + while new_name.chars().last() == Some('/') && new_name.len() != 1 { + new_name.pop(); + } + let (hp, data) = examine_dir_str(new_name); + permissions = permissions && hp; + results.push(data); + } + (permissions, results) +} + +fn examine_dir_str(loc: String) -> (bool, Node) { + let mut inodes: HashSet = HashSet::new(); + let (hp, result) = examine_dir(fs::read_dir(&loc), &mut inodes); + + // This needs to be folded into the below recursive call somehow + let new_size = result.iter().fold(0, |a, b| a + b.dir.size); + ( + hp, + Node { + dir: DirEnt { + name: loc, + size: new_size, + }, + children: result, + }, + ) +} + +#[cfg(target_os = "linux")] +fn get_metadata_blocks_and_inode(d: &std::fs::DirEntry) -> Option<(u64, u64)> { + use std::os::linux::fs::MetadataExt; + match d.metadata().ok() { + Some(md) => Some((md.len(), md.st_ino())), + None => None, + } +} + +#[cfg(target_os = "unix")] +fn get_metadata_blocks_and_inode(d: &std::fs::DirEntry) -> Option<(u64, u64)> { + use std::os::unix::fs::MetadataExt; + match d.metadata().ok() { + Some(md) => Some((md.len(), md.ino())), + None => None, + } +} + +#[cfg(target_os = "macos")] +fn get_metadata_blocks_and_inode(d: &std::fs::DirEntry) -> Option<(u64, u64)> { + use std::os::macos::fs::MetadataExt; + match d.metadata().ok() { + Some(md) => Some((md.len(), md.st_ino())), + None => None, + } +} + +#[cfg(not(any(target_os = "linux", target_os = "unix", target_os = "macos")))] +fn get_metadata_blocks_and_inode(_d: &std::fs::DirEntry) -> Option<(u64, u64)> { + match _d.metadata().ok() { + Some(md) => Some((md.len(), 0)), //move to option not 0 + None => None, + } +} + +fn examine_dir(a_dir: io::Result, inodes: &mut HashSet) -> (bool, Vec) { + let mut result = vec![]; + let mut have_permission = true; + + if a_dir.is_ok() { + let paths = a_dir.unwrap(); + for dd in paths { + match dd { + Ok(d) => { + let file_type = d.file_type().ok(); + let maybe_size_and_inode = get_metadata_blocks_and_inode(&d); + + match (file_type, maybe_size_and_inode) { + (Some(file_type), Some((size, inode))) => { + let s = d.path().to_string_lossy().to_string(); + if inodes.contains(&inode) { + continue; + } + inodes.insert(inode); + + if d.path().is_dir() && !file_type.is_symlink() { + let (hp, recursive) = examine_dir(fs::read_dir(d.path()), inodes); + have_permission = have_permission && hp; + let new_size = recursive.iter().fold(size, |a, b| a + b.dir.size); + result.push(Node { + dir: DirEnt { + name: s, + size: new_size, + }, + children: recursive, + }) + } else { + result.push(Node { + dir: DirEnt { + name: s, + size: size, + }, + children: vec![], + }) + } + } + (_, None) => have_permission = false, + (_, _) => (), + } + } + Err(_) => (), + } + } + } else { + have_permission = false; + } + (have_permission, result) +} + +// We start with a list of root directories - these must be the biggest folders +// We then repeadedly merge in the children of the biggest directory - Each iteration +// the next biggest directory's children are merged in. +pub fn find_big_ones<'a>(l: &'a Vec, max_to_show: usize) -> Vec<&Node> { + let mut new_l: Vec<&Node> = l.iter().map(|a| a).collect(); + new_l.sort(); + + for processed_pointer in 0..max_to_show { + if new_l.len() == processed_pointer { + break; + } + // Must be a list of pointers into new_l otherwise b_list will go out of scope + // when it is deallocated + let mut b_list: Vec<&Node> = new_l[processed_pointer] + .children + .iter() + .map(|a| a) + .collect(); + new_l.extend(b_list); + new_l.sort(); + } + + if new_l.len() > max_to_show { + new_l[0..max_to_show + 1].to_vec() + } else { + new_l + } +} + +pub fn display(permissions: bool, to_display: &Vec<&Node>) -> () { + if !permissions { + eprintln!("Did not have permissions for all directories"); + } + + display_node(to_display[0], &to_display, true, 1, "") +} + +fn display_node>( + node_to_print: &Node, + to_display: &Vec<&Node>, + is_first: bool, + depth: u8, + indentation_str: S, +) { + let mut is = indentation_str.into(); + print_this_node(node_to_print, is_first, depth, is.as_ref()); + + is = is.replace("└─┬", " "); + is = is.replace("└──", " "); + is = is.replace("├──", "│ "); + is = is.replace("├─┬", "│ "); + + let printable_node_slashes = node_to_print.dir.name.matches('/').count(); + + let mut num_siblings = to_display.iter().fold(0, |a, b| { + if node_to_print.children.contains(b) + && b.dir.name.matches('/').count() == printable_node_slashes + 1 + { + a + 1 + } else { + a + } + }); + + let mut is_biggest = true; + let mut has_display_children = false; + for node in to_display { + if node_to_print.children.contains(node) { + let has_children = node.children.len() > 0; + if node.dir.name.matches("/").count() == printable_node_slashes + 1 { + num_siblings -= 1; + for ref n in node.children.iter() { + has_display_children = has_display_children || to_display.contains(n); + } + let has_children = has_children && has_display_children; + let tree_chars = { + if num_siblings == 0 { + if has_children { + "└─┬" + } else { + "└──" + } + } else { + if has_children { + "├─┬" + } else { + "├──" + } + } + }; + display_node( + &node, + to_display, + is_biggest, + depth + 1, + is.to_string() + tree_chars, + ); + is_biggest = false; + } + } + } +} + +fn print_this_node(node_to_print: &Node, is_biggest: bool, depth: u8, indentation_str: &str) { + let padded_size = format!("{:>5}", human_readable_number(node_to_print.dir.size),); + println!( + "{} {} {}", + if is_biggest { + Fixed(196).paint(padded_size) + } else { + Fixed(7).paint(padded_size) + }, + indentation_str, + Fixed(7) + .on(Fixed(cmp::min(8, (depth) as u8) + 231)) + .paint(node_to_print.dir.name.to_string()) + ); +} + +fn human_readable_number(size: u64) -> (String) { + let units = vec!["T", "G", "M", "K"]; //make static + + //return format!("{}B", size); + + for (i, u) in units.iter().enumerate() { + let marker = 1024u64.pow((units.len() - i) as u32); + if size >= marker { + if size / marker < 10 { + return format!("{:.1}{}", (size as f32 / marker as f32), u); + } else { + return format!("{}{}", (size / marker), u); + } + } + } + return format!("{}B", size); +} + +mod tests { + #[allow(unused_imports)] + use super::*; + + #[test] + fn test_human_readable_number() { + assert_eq!(human_readable_number(1), "1B"); + assert_eq!(human_readable_number(956), "956B"); + assert_eq!(human_readable_number(1004), "1004B"); + assert_eq!(human_readable_number(1024), "1.0K"); + assert_eq!(human_readable_number(1536), "1.5K"); + assert_eq!(human_readable_number(1024 * 512), "512K"); + assert_eq!(human_readable_number(1024 * 1024), "1.0M"); + assert_eq!(human_readable_number(1024 * 1024 * 1024 - 1), "1023M"); + assert_eq!(human_readable_number(1024 * 1024 * 1024 * 20), "20G"); + assert_eq!(human_readable_number(1024 * 1024 * 1024 * 1024), "1.0T"); + } +} From aa963defdac63e71d726abe5fdc8446d784f9a5a Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Tue, 20 Mar 2018 22:50:54 -0700 Subject: [PATCH 3/4] update sample output with new box-drawing characters --- README.md | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 142722b..5d67764 100644 --- a/README.md +++ b/README.md @@ -16,27 +16,22 @@ Usage: dust -n 30 (Shows 30 directories not 15) ``` -dust . - 161M . - 160M └── ./target - 123M ├── ./target/debug - 83M │ ├── ./target/debug/deps - 16M │ │ ├── ./target/debug/deps/libclap-82e6176feef5d4b7.rlib - 8.6M │ │ └── ./target/debug/deps/dust-993f7d919d92f0f8.dSYM - 8.6M │ │ └── ./target/debug/deps/dust-993f7d919d92f0f8.dSYM/Contents - 8.6M │ │ └── ./target/debug/deps/dust-993f7d919d92f0f8.dSYM/Contents/Resources - 27M │ ├── ./target/debug/incremental - 12M │ └── ./target/debug/build - 20M ├── ./target/x86_64-apple-darwin - 20M │ └── ./target/x86_64-apple-darwin/debug - 20M │ └── ./target/x86_64-apple-darwin/debug/deps - 16M │ └── ./target/x86_64-apple-darwin/debug/deps/libclap-7e3f8513c52cd558.rlib - 16M └── ./target/release - 13M └── ./target/release/deps +djin:git/dust> dust + 65M . + 65M └─┬ ./target + 49M ├─┬ ./target/debug + 26M │ ├─┬ ./target/debug/deps + 21M │ │ └── ./target/debug/deps/libclap-9e6625ac8ff074ad.rlib + 13M │ ├── ./target/debug/dust + 8.9M │ └─┬ ./target/debug/incremental + 6.7M │ ├─┬ ./target/debug/incremental/dust-2748eiei2tcnp + 6.7M │ │ └─┬ ./target/debug/incremental/dust-2748eiei2tcnp/s-ezd6jnik5u-163pyem-1aab9ncf5glum + 3.0M │ │ └── ./target/debug/incremental/dust-2748eiei2tcnp/s-ezd6jnik5u-163pyem-1aab9ncf5glum/dep-graph.bin + 2.2M │ └─┬ ./target/debug/incremental/dust-1dlon65p8m3vl + 2.2M │ └── ./target/debug/incremental/dust-1dlon65p8m3vl/s-ezd6jncecv-1xsnfd0-4dw9l1r2th2t + 15M └─┬ ./target/release + 9.2M ├─┬ ./target/release/deps + 6.7M │ └── ./target/release/deps/libclap-87bc2534ea57f044.rlib + 5.9M └── ./target/release/dust ``` - Performance: dust is currently about 4 times slower than du. - - - - From 381d2868471cf73de1d0d97ac6ee7cf188cfbdbb Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Wed, 21 Mar 2018 00:09:23 -0700 Subject: [PATCH 4/4] use accessor and creator methods for new types in lib --- src/lib.rs | 56 +++++++++++++++++++++++++++++++++++++---------- src/utils.rs | 62 ++++++++++++++++++---------------------------------- 2 files changed, 66 insertions(+), 52 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5deb080..68555e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,34 +2,68 @@ use std::cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd}; #[derive(Clone, Debug)] pub struct Node { - pub dir: DirEnt, - pub children: Vec, + entry: DirEnt, + children: Vec, } #[derive(Clone, Debug)] pub struct DirEnt { - pub name: String, - pub size: u64, + name: String, + size: u64, +} + +impl Node { + pub fn new(entry: DirEnt, children: Vec) -> Self { + Node { + entry: entry, + children: children, + } + } + + pub fn children(&self) -> &Vec { + &self.children + } + + pub fn entry(&self) -> &DirEnt { + &self.entry + } +} + +impl DirEnt { + pub fn new(name: &str, size: u64) -> Self { + DirEnt { + name: String::from(name), + size: size, + } + } + + pub fn name(&self) -> &String { + &self.name + } + + pub fn size(&self) -> u64 { + self.size + } } impl Ord for Node { fn cmp(&self, other: &Self) -> Ordering { - if self.dir.size > other.dir.size { + if self.entry.size > other.entry.size { Ordering::Less - } else if self.dir.size < other.dir.size { + } else if self.entry.size < other.entry.size { Ordering::Greater } else { - let my_slashes = self.dir.name.matches('/').count(); - let other_slashes = other.dir.name.matches('/').count(); + let my_slashes = self.entry.name.matches('/').count(); + let other_slashes = other.entry.name.matches('/').count(); if my_slashes > other_slashes { Ordering::Greater } else if my_slashes < other_slashes { Ordering::Less } else { - if self.dir.name < other.dir.name { + if self.entry.name < other.entry.name { Ordering::Less - } else if self.dir.name > other.dir.name { + } else if self.entry.name > other.entry.name { Ordering::Greater } else { Ordering::Equal @@ -45,7 +79,7 @@ impl PartialOrd for Node { } impl PartialEq for Node { fn eq(&self, other: &Self) -> bool { - (&self.dir.name, self.dir.size) == (&other.dir.name, other.dir.size) + (&self.entry.name, self.entry.size) == (&other.entry.name, other.entry.size) } } impl Eq for Node {} diff --git a/src/utils.rs b/src/utils.rs index fa16ce1..f0dab5e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -13,34 +13,25 @@ use self::ansi_term::Colour::Fixed; pub fn get_dir_tree(filenames: &Vec<&str>) -> (bool, Vec) { let mut permissions = true; let mut results = vec![]; - for b in filenames { - let mut new_name = String::from(*b); + for &b in filenames { + let mut new_name = String::from(b); while new_name.chars().last() == Some('/') && new_name.len() != 1 { new_name.pop(); } - let (hp, data) = examine_dir_str(new_name); + let (hp, data) = examine_dir_str(&new_name); permissions = permissions && hp; results.push(data); } (permissions, results) } -fn examine_dir_str(loc: String) -> (bool, Node) { +fn examine_dir_str(loc: &str) -> (bool, Node) { let mut inodes: HashSet = HashSet::new(); - let (hp, result) = examine_dir(fs::read_dir(&loc), &mut inodes); + let (hp, result) = examine_dir(fs::read_dir(loc), &mut inodes); // This needs to be folded into the below recursive call somehow - let new_size = result.iter().fold(0, |a, b| a + b.dir.size); - ( - hp, - Node { - dir: DirEnt { - name: loc, - size: new_size, - }, - children: result, - }, - ) + let new_size = result.iter().fold(0, |a, b| a + b.entry().size()); + (hp, Node::new(DirEnt::new(loc, new_size), result)) } #[cfg(target_os = "linux")] @@ -101,22 +92,11 @@ fn examine_dir(a_dir: io::Result, inodes: &mut HashSet) -> (bool, if d.path().is_dir() && !file_type.is_symlink() { let (hp, recursive) = examine_dir(fs::read_dir(d.path()), inodes); have_permission = have_permission && hp; - let new_size = recursive.iter().fold(size, |a, b| a + b.dir.size); - result.push(Node { - dir: DirEnt { - name: s, - size: new_size, - }, - children: recursive, - }) + let new_size = + recursive.iter().fold(size, |a, b| a + b.entry().size()); + result.push(Node::new(DirEnt::new(&s, new_size), recursive)) } else { - result.push(Node { - dir: DirEnt { - name: s, - size: size, - }, - children: vec![], - }) + result.push(Node::new(DirEnt::new(&s, size), vec![])) } } (_, None) => have_permission = false, @@ -146,7 +126,7 @@ pub fn find_big_ones<'a>(l: &'a Vec, max_to_show: usize) -> Vec<&Node> { // Must be a list of pointers into new_l otherwise b_list will go out of scope // when it is deallocated let mut b_list: Vec<&Node> = new_l[processed_pointer] - .children + .children() .iter() .map(|a| a) .collect(); @@ -184,11 +164,11 @@ fn display_node>( is = is.replace("├──", "│ "); is = is.replace("├─┬", "│ "); - let printable_node_slashes = node_to_print.dir.name.matches('/').count(); + let printable_node_slashes = node_to_print.entry().name().matches('/').count(); let mut num_siblings = to_display.iter().fold(0, |a, b| { - if node_to_print.children.contains(b) - && b.dir.name.matches('/').count() == printable_node_slashes + 1 + if node_to_print.children().contains(b) + && b.entry().name().matches('/').count() == printable_node_slashes + 1 { a + 1 } else { @@ -199,11 +179,11 @@ fn display_node>( let mut is_biggest = true; let mut has_display_children = false; for node in to_display { - if node_to_print.children.contains(node) { - let has_children = node.children.len() > 0; - if node.dir.name.matches("/").count() == printable_node_slashes + 1 { + if node_to_print.children().contains(node) { + let has_children = node.children().len() > 0; + if node.entry().name().matches("/").count() == printable_node_slashes + 1 { num_siblings -= 1; - for ref n in node.children.iter() { + for ref n in node.children().iter() { has_display_children = has_display_children || to_display.contains(n); } let has_children = has_children && has_display_children; @@ -236,7 +216,7 @@ fn display_node>( } fn print_this_node(node_to_print: &Node, is_biggest: bool, depth: u8, indentation_str: &str) { - let padded_size = format!("{:>5}", human_readable_number(node_to_print.dir.size),); + let padded_size = format!("{:>5}", human_readable_number(node_to_print.entry().size()),); println!( "{} {} {}", if is_biggest { @@ -247,7 +227,7 @@ fn print_this_node(node_to_print: &Node, is_biggest: bool, depth: u8, indentatio indentation_str, Fixed(7) .on(Fixed(cmp::min(8, (depth) as u8) + 231)) - .paint(node_to_print.dir.name.to_string()) + .paint(node_to_print.entry().name().to_string()) ); }