diff --git a/Cargo.lock b/Cargo.lock index 5d0ff9c..b3da485 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,7 +22,7 @@ dependencies = [ "difference 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "environment 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", "skeptic 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -88,7 +88,7 @@ dependencies = [ "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -141,7 +141,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "du-dust" -version = "0.3.2" +version = "0.4.0" dependencies = [ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "assert_cli 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -230,7 +230,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "proc-macro2" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -249,7 +249,7 @@ name = "quote" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -382,14 +382,14 @@ name = "serde_derive" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_json" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -407,7 +407,7 @@ dependencies = [ "error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "pulldown-cmark 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -422,7 +422,7 @@ name = "syn" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -560,7 +560,7 @@ dependencies = [ "checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" "checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" -"checksum proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afdc77cc74ec70ed262262942ebb7dac3d479e9e5cfa2da1841c0806f6cdabcc" +"checksum proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "90cf5f418035b98e655e9cdb225047638296b862b42411c4e45bb88d700f7fc0" "checksum pulldown-cmark 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eef52fac62d0ea7b9b4dc7da092aa64ea7ec3d90af6679422d3d7e0e14b6ee15" "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" "checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" @@ -581,7 +581,7 @@ dependencies = [ "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd" "checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e" -"checksum serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "051c49229f282f7c6f3813f8286cc1e3323e8051823fce42c7ea80fe13521704" +"checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2" "checksum skeptic 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6fb8ed853fdc19ce09752d63f3a2e5b5158aeb261520cd75eb618bd60305165" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" "checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" diff --git a/Cargo.toml b/Cargo.toml index aa1051d..f76d81c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "du-dust" description = "A more intuitive version of du" -version = "0.3.2" +version = "0.4.0" authors = ["bootandy ", "nebkor "] documentation = "https://github.com/bootandy/dust" diff --git a/src/display.rs b/src/display.rs index 63e7418..da78bbf 100644 --- a/src/display.rs +++ b/src/display.rs @@ -2,166 +2,138 @@ extern crate ansi_term; use self::ansi_term::Colour::Fixed; use self::ansi_term::Style; -use std::collections::HashSet; -use utils::{ensure_end_slash, strip_end_slash}; +use utils::Node; static UNITS: [char; 4] = ['T', 'G', 'M', 'K']; -pub fn draw_it( - permissions: bool, - short_paths: bool, - depth: Option, - base_dirs: HashSet, - to_display: Vec<(String, u64)>, -) { +pub struct DisplayData { + pub short_paths: bool, + pub is_reversed: bool, +} + +impl DisplayData { + fn get_first_chars(&self) -> &str { + if self.is_reversed { + "─┴" + } else { + "─┬" + } + } + + fn get_tree_chars( + &self, + num_siblings: u64, + max_siblings: u64, + has_children: bool, + ) -> &'static str { + if self.is_reversed { + if num_siblings == max_siblings - 1 { + if has_children { + "┌─┴" + } else { + "┌──" + } + } else if has_children { + "├─┴" + } else { + "├──" + } + } else { + if num_siblings == 0 { + if has_children { + "└─┬" + } else { + "└──" + } + } else if has_children { + "├─┬" + } else { + "├──" + } + } + } + + fn is_biggest(&self, num_siblings: u64, max_siblings: u64) -> bool { + if self.is_reversed { + num_siblings == 0 + } else { + num_siblings == max_siblings - 1 + } + } + + fn get_children_from_node(&self, node: Node) -> impl Iterator> { + if self.is_reversed { + let n: Vec> = node.children.into_iter().rev().map(|a| a).collect(); + return n.into_iter(); + } else { + return node.children.into_iter(); + } + } +} + +pub fn draw_it(permissions: bool, use_full_path: bool, is_reversed: bool, root_node: Node) { if !permissions { eprintln!("Did not have permissions for all directories"); } - let mut found = HashSet::new(); - - for &(ref k, _) in to_display.iter() { - if base_dirs.contains(k) { - display_node(&k, &mut found, &to_display, true, short_paths, depth, "─┬"); - } - } -} - -fn get_size(nodes: &[(String, u64)], node_to_print: &str) -> Option { - for &(ref k, ref v) in nodes.iter() { - if *k == *node_to_print { - return Some(*v); - } - } - None -} - -fn display_node>( - node_to_print: &str, - found: &mut HashSet, - to_display: &[(String, u64)], - is_biggest: bool, - short_paths: bool, - depth: Option, - indentation_str: S, -) { - if found.contains(node_to_print) { - return; - } - found.insert(node_to_print.to_string()); - - let new_depth = match depth { - None => None, - Some(0) => return, - Some(d) => Some(d - 1), + let display_data = DisplayData { + short_paths: !use_full_path, + is_reversed, }; - match get_size(to_display, node_to_print) { - None => println!("Can not find path: {}", node_to_print), - Some(size) => { - let is = indentation_str.into(); - print_this_node(node_to_print, size, is_biggest, short_paths, is.as_ref()); - let new_indent = clean_indentation_string(is); - let ntp_with_slash = strip_end_slash(node_to_print); - - // Annoying edge case for when run on root directory - let num_slashes = if ntp_with_slash == "/" { - 1 - } else { - ntp_with_slash.matches('/').count() + 1 - }; - let mut num_siblings = count_siblings(to_display, num_slashes - 1, node_to_print); - - let mut is_biggest = true; - for &(ref k, _) in to_display.iter() { - let temp = String::from(ensure_end_slash(node_to_print)); - if k.starts_with(temp.as_str()) && k.matches('/').count() == num_slashes { - num_siblings -= 1; - let has_children = has_children(to_display, new_depth, k, num_slashes); - display_node( - k, - found, - to_display, - is_biggest, - short_paths, - new_depth, - new_indent.to_string() + get_tree_chars(num_siblings != 0, has_children), - ); - is_biggest = false; - } - } - } + for c in display_data.get_children_from_node(root_node) { + let first_tree_chars = display_data.get_first_chars(); + display_node(*c, true, first_tree_chars, &display_data) } } -fn clean_indentation_string>(s: S) -> String { - let mut is = s.into(); +fn display_node(node: Node, is_biggest: bool, indent: &str, display_data: &DisplayData) { + let short = display_data.short_paths; + + let mut num_siblings = node.children.len() as u64; + let max_sibling = num_siblings; + let new_indent = clean_indentation_string(indent); + let name = node.name.clone(); + let size = node.size; + + if !display_data.is_reversed { + print_this_node(&*name, size, is_biggest, short, indent); + } + + for c in display_data.get_children_from_node(node) { + num_siblings -= 1; + let chars = display_data.get_tree_chars(num_siblings, max_sibling, c.children.len() > 0); + let is_biggest = display_data.is_biggest(num_siblings, max_sibling); + let full_indent = new_indent.clone() + chars; + display_node(*c, is_biggest, &*full_indent, display_data) + } + + if display_data.is_reversed { + print_this_node(&*name, size, is_biggest, short, indent); + } +} + +fn clean_indentation_string(s: &str) -> String { + let mut is: String = s.into(); + // For reversed: + is = is.replace("┌─┴", " "); + is = is.replace("┌──", " "); + is = is.replace("├─┴", "│ "); + is = is.replace("─┴", " "); + // For normal is = is.replace("└─┬", " "); is = is.replace("└──", " "); - is = is.replace("├──", "│ "); is = is.replace("├─┬", "│ "); is = is.replace("─┬", " "); + // For both + is = is.replace("├──", "│ "); is } -fn count_siblings(to_display: &[(String, u64)], num_slashes: usize, ntp: &str) -> u64 { - to_display.iter().fold(0, |a, b| { - if b.0.starts_with(ntp) && b.0.as_str().matches('/').count() == num_slashes + 1 { - a + 1 - } else { - a - } - }) -} - -fn has_children( - to_display: &[(String, u64)], - new_depth: Option, - ntp: &str, - num_slashes: usize, -) -> bool { - if new_depth.is_none() || new_depth.unwrap() != 1 { - for &(ref k2, _) in to_display.iter() { - let ntp_with_slash = String::from(ntp.to_owned() + "/"); - if k2.starts_with(ntp_with_slash.as_str()) && k2.matches('/').count() == num_slashes + 1 - { - return true; - } - } - } - false -} - -fn get_tree_chars(has_smaller_siblings: bool, has_children: bool) -> &'static str { - if !has_smaller_siblings { - if has_children { - "└─┬" - } else { - "└──" - } - } else if has_children { - "├─┬" - } else { - "├──" - } -} - -fn print_this_node( - node_name: &str, - size: u64, - is_biggest: bool, - short_paths: bool, - indentation: &str, -) { +fn print_this_node(name: &str, size: u64, is_biggest: bool, short_paths: bool, indentation: &str) { let pretty_size = format!("{:>5}", human_readable_number(size),); println!( "{}", - format_string( - node_name, - is_biggest, - short_paths, - pretty_size.as_ref(), - indentation - ) + format_string(name, is_biggest, short_paths, &*pretty_size, indentation) ) } diff --git a/src/main.rs b/src/main.rs index 5e4888a..3fc2d4f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ extern crate walkdir; use self::display::draw_it; use clap::{App, AppSettings, Arg}; -use utils::{find_big_ones, get_dir_tree, simplify_dir_names, sort, trim_deep_ones}; +use utils::{find_big_ones, get_dir_tree, simplify_dir_names, sort, trim_deep_ones, Node}; mod display; mod utils; @@ -43,6 +43,12 @@ fn main() { .long("apparent-size") .help("If set will use file length. Otherwise we use blocks"), ) + .arg( + Arg::with_name("reverse") + .short("r") + .long("reverse") + .help("If applied tree will be printed upside down (biggest lowest)"), + ) .arg(Arg::with_name("inputs").multiple(true)) .get_matches(); @@ -91,14 +97,50 @@ fn main() { Some(d) => trim_deep_ones(sorted_data, d, &simplified_dirs), } }; + let tree = build_tree(biggest_ones, depth); + //println!("{:?}", tree); + draw_it( permissions, - !use_full_path, - depth, - simplified_dirs, - biggest_ones, + use_full_path, + options.is_present("reverse"), + tree, ); } +fn build_tree(biggest_ones: Vec<(String, u64)>, depth: Option) -> Node { + let mut top_parent = Node { + name: "".to_string(), + size: 0, + children: vec![], + }; + + // assume sorted order + for b in biggest_ones { + let n = Node { + name: b.0, + size: b.1, + children: vec![], + }; + recursively_build_tree(&mut top_parent, n, depth) + } + top_parent +} + +fn recursively_build_tree(parent_node: &mut Node, new_node: Node, depth: Option) { + let new_depth = match depth { + None => None, + Some(0) => return, + Some(d) => Some(d - 1), + }; + for c in parent_node.children.iter_mut() { + if new_node.name.starts_with(&c.name) { + return recursively_build_tree(&mut *c, new_node, new_depth); + } + } + let temp = Box::::new(new_node); + parent_node.children.push(temp); +} + #[cfg(test)] mod tests; diff --git a/src/tests.rs b/src/tests.rs index 0e72534..096c128 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -109,6 +109,27 @@ pub fn test_apparent_size() { .unwrap(); } +#[test] +pub fn test_reverse_flag() { + // variable names the same length make the output easier to read + let a = " ┌── a_file"; + let b = " ├── hello_file"; + let c = " ┌─┴ many"; + let d = " ─┴ test_dir"; + + assert_cli::Assert::main_binary() + .with_args(&["-r", "src/test_dir"]) + .stdout() + .contains(a) + .stdout() + .contains(b) + .stdout() + .contains(c) + .stdout() + .contains(d) + .unwrap(); +} + #[test] pub fn test_d_flag_works() { // We should see the top level directory but not the sub dirs / files: diff --git a/src/utils/mod.rs b/src/utils/mod.rs index e33f08d..6ab741d 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -7,6 +7,13 @@ use walkdir::WalkDir; mod platform; use self::platform::*; +#[derive(Debug)] +pub struct Node { + pub name: String, + pub size: u64, + pub children: Vec>, +} + pub fn simplify_dir_names(filenames: Vec<&str>) -> HashSet { let mut top_level_names: HashSet = HashSet::new(); @@ -85,6 +92,7 @@ fn examine_dir( inodes.insert(inode_dev_pair); } } + // This path and all its parent paths have their counter incremented let mut e_path = e.path().to_path_buf(); loop { let path_name = e_path.to_string_lossy().to_string(); @@ -104,7 +112,8 @@ fn examine_dir( } } } -pub fn compare_tuple(a: &(String, u64), b: &(String, u64)) -> Ordering { + +pub fn sort_by_size_first_name_second(a: &(String, u64), b: &(String, u64)) -> Ordering { let result = b.1.cmp(&a.1); if result == Ordering::Equal { a.0.cmp(&b.0) @@ -115,7 +124,7 @@ pub fn compare_tuple(a: &(String, u64), b: &(String, u64)) -> Ordering { pub fn sort(data: HashMap) -> Vec<(String, u64)> { let mut new_l: Vec<(String, u64)> = data.iter().map(|(a, b)| (a.clone(), *b)).collect(); - new_l.sort_by(|a, b| compare_tuple(&a, &b)); + new_l.sort_by(|a, b| sort_by_size_first_name_second(&a, &b)); new_l }