From d059e974164ac267ebba5835c1abe54664abe0d4 Mon Sep 17 00:00:00 2001 From: Luzian Hahn Date: Tue, 26 Aug 2025 10:29:35 +0200 Subject: [PATCH 1/5] feat: add --aggregate flag to allow "du -sh *"-usage see also https://github.com/sharkdp/diskus/issues/3 --- Cargo.lock | 426 ++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 4 + src/main.rs | 45 ++++- src/walk.rs | 3 + tests/aggregate.rs | 47 +++++ 5 files changed, 521 insertions(+), 4 deletions(-) create mode 100644 tests/aggregate.rs diff --git a/Cargo.lock b/Cargo.lock index 6396564..d72b97b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -11,12 +20,34 @@ dependencies = [ "winapi", ] +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "assert_cmd" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bd389a4b2970a01282ee455294913c0a43724daedcd1a24c3eb0ec1c1320b66" +dependencies = [ + "anstyle", + "bstr", + "doc-comment", + "libc", + "predicates 3.1.3", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "atty" version = "0.2.14" @@ -28,12 +59,41 @@ dependencies = [ "winapi", ] +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" + +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + [[package]] name = "clap" version = "2.34.0" @@ -42,7 +102,7 @@ checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", - "bitflags", + "bitflags 1.3.2", "strsim", "term_size", "textwrap", @@ -84,32 +144,84 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "diskus" version = "0.8.0" dependencies = [ + "assert_cmd", "atty", "clap", "crossbeam-channel", "humansize", "num-format", "num_cpus", + "predicates 2.1.5", "rayon", "tempdir", + "tempfile", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi", +] + [[package]] name = "hermit-abi" version = "0.1.19" @@ -131,6 +243,15 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.14" @@ -143,6 +264,24 @@ version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "num-format" version = "0.4.4" @@ -153,6 +292,15 @@ dependencies = [ "itoa", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -163,6 +311,77 @@ dependencies = [ "libc", ] +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "predicates" +version = "2.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" +dependencies = [ + "difflib", + "float-cmp", + "itertools", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +dependencies = [ + "anstyle", + "difflib", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" + +[[package]] +name = "predicates-tree" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.4.6" @@ -220,6 +439,35 @@ dependencies = [ "rand_core 0.3.1", ] +[[package]] +name = "regex" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + [[package]] name = "remove_dir_all" version = "0.5.3" @@ -229,12 +477,56 @@ dependencies = [ "winapi", ] +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags 2.9.3", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "tempdir" version = "0.3.7" @@ -245,6 +537,19 @@ dependencies = [ "remove_dir_all", ] +[[package]] +name = "tempfile" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys", +] + [[package]] name = "term_size" version = "0.3.2" @@ -255,6 +560,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + [[package]] name = "textwrap" version = "0.11.0" @@ -265,6 +576,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + [[package]] name = "unicode-width" version = "0.1.14" @@ -277,6 +594,24 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "winapi" version = "0.3.9" @@ -298,3 +633,92 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.3", +] diff --git a/Cargo.toml b/Cargo.toml index 10fb68e..8fc5bdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,10 @@ features = ["suggestions", "color", "wrap_help"] [dev-dependencies] tempdir = "0.3" +assert_cmd = "2.0" +tempfile = "3.6" +predicates = "2.1" + [[bin]] name = "diskus" diff --git a/src/main.rs b/src/main.rs index 693003d..11bea25 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,6 +42,31 @@ fn print_result(size: u64, errors: &[Error], size_format: &FileSizeOpts, verbose } } + +fn perform_walks(walks: Vec, aggregate: bool, size_format: FileSizeOpts, verbose: bool) { + if aggregate { + let mut total_size = 0; + let mut all_errors = Vec::new(); + + for walk in walks { + let (size, errors) = walk.run(); + total_size += size; + all_errors.extend(errors); + } + + print_result(total_size, &all_errors, &size_format, verbose); + } else { + for walk in walks { + // each Walk knows its own root_directories + let (size, errors) = walk.run(); + for path in walk.get_root_directories() { + println!("{}:", path.display()); + } + print_result(size, &errors, &size_format, verbose); + } + } +} + fn main() { let app = App::new(crate_name!()) .setting(AppSettings::ColorAuto) @@ -78,6 +103,13 @@ fn main() { .short("v") .takes_value(false) .help("Do not hide filesystem errors"), + ) + .arg( + Arg::with_name("aggregate") + .long("aggregate") + .short("a") + .takes_value(false) + .help("Aggregate sizes across all provided paths"), ); #[cfg(not(windows))] @@ -117,8 +149,15 @@ fn main() { }; let verbose = matches.is_present("verbose"); + let aggregate = matches.is_present("aggregate"); + let walks: Vec = if aggregate { + vec![Walk::new(&paths, num_threads, filesize_type)] + } else { + paths + .iter() + .map(|p| Walk::new(std::slice::from_ref(p), num_threads, filesize_type)) + .collect() + }; - let walk = Walk::new(&paths, num_threads, filesize_type); - let (size, errors) = walk.run(); - print_result(size, &errors, &size_format, verbose); + perform_walks(walks, aggregate, size_format, verbose); } diff --git a/src/walk.rs b/src/walk.rs index eed97b6..4662a29 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -76,6 +76,9 @@ impl Walk<'_> { filesize_type, } } + pub fn get_root_directories(&self) -> &[PathBuf] { + self.root_directories + } pub fn run(&self) -> (u64, Vec) { let (tx, rx) = channel::unbounded(); diff --git a/tests/aggregate.rs b/tests/aggregate.rs new file mode 100644 index 0000000..779a6d8 --- /dev/null +++ b/tests/aggregate.rs @@ -0,0 +1,47 @@ +use assert_cmd::Command; +use tempfile::tempdir; +use std::fs::{self, File}; +use std::io::Write; + +#[test] +fn test_diskus_output_lines() { + let tmp = tempdir().unwrap(); + let dir1 = tmp.path().join("dir1"); + let dir2 = tmp.path().join("dir2"); + fs::create_dir(&dir1).unwrap(); + fs::create_dir(&dir2).unwrap(); + + let mut f1 = File::create(dir1.join("a.txt")).unwrap(); + f1.write_all(&vec![0u8; 100]).unwrap(); + let mut f2 = File::create(dir2.join("b.txt")).unwrap(); + f2.write_all(&vec![0u8; 200]).unwrap(); + + // Testing for exact outputs, i.e. filesizes is problematic across platforms. + // Hence we just verify, that the amount of printed lines varies depending on aggregation. + + // ---- Default mode (no aggregation) ---- + let mut cmd = Command::cargo_bin("diskus").unwrap(); + cmd.arg(dir1.to_str().unwrap()) + .arg(dir2.to_str().unwrap()); + + let output = cmd.assert().success().get_output().stdout.clone(); + let out_str = String::from_utf8_lossy(&output); + + // Each path prints one header line + one size line + // e.g., "dir1:\n4196 bytes\n" + let lines: Vec<&str> = out_str.lines().collect(); + assert_eq!(lines.len(), 4); // 2 directories × 2 lines each (header + size) + + // ---- Aggregate mode (-a) ---- + let mut cmd = Command::cargo_bin("diskus").unwrap(); + cmd.arg("-a") + .arg(dir1.to_str().unwrap()) + .arg(dir2.to_str().unwrap()); + + let output = cmd.assert().success().get_output().stdout.clone(); + let out_str = String::from_utf8_lossy(&output); + + let lines: Vec<&str> = out_str.lines().collect(); + assert_eq!(lines.len(), 1); // Only one line for aggregated size +} + From d00bf43421bc88affaa9239ac392460ca9dd1020 Mon Sep 17 00:00:00 2001 From: Luzian Hahn Date: Tue, 26 Aug 2025 17:06:09 +0200 Subject: [PATCH 2/5] feat: apply "du"-like formatting --- Cargo.lock | 20 ++++++++++++++++++-- Cargo.toml | 1 + src/main.rs | 34 ++++++++++++++++++++++------------ tests/aggregate.rs | 4 ++-- 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d72b97b..c0161da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,7 +106,7 @@ dependencies = [ "strsim", "term_size", "textwrap", - "unicode-width", + "unicode-width 0.1.14", "vec_map", ] @@ -163,6 +163,7 @@ dependencies = [ "num_cpus", "predicates 2.1.5", "rayon", + "tabwriter", "tempdir", "tempfile", ] @@ -527,6 +528,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tabwriter" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce91f2f0ec87dff7e6bcbbeb267439aa1188703003c6055193c821487400432" +dependencies = [ + "unicode-width 0.2.1", +] + [[package]] name = "tempdir" version = "0.3.7" @@ -573,7 +583,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ "term_size", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -588,6 +598,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + [[package]] name = "vec_map" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index 8fc5bdb..f41aa5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ num-format = "0.4" rayon = "1.0" crossbeam-channel = "0.5" atty = "0.2" +tabwriter = "1.4.1" [dependencies.clap] version = "2" diff --git a/src/main.rs b/src/main.rs index 11bea25..299a0e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,15 @@ use std::path::PathBuf; +use std::io::{self, Write}; use clap::{crate_name, crate_version, App, AppSettings, Arg}; use humansize::file_size_opts::{self, FileSizeOpts}; use humansize::FileSize; use num_format::{Locale, ToFormattedString}; +use tabwriter::TabWriter; use diskus::{Error, FilesizeType, Walk}; -fn print_result(size: u64, errors: &[Error], size_format: &FileSizeOpts, verbose: bool) { +fn build_message(path: Option<&PathBuf>, size: u64, errors: &[Error], size_format: &FileSizeOpts, verbose: bool) -> String { if verbose { for err in errors { match err { @@ -31,14 +33,17 @@ fn print_result(size: u64, errors: &[Error], size_format: &FileSizeOpts, verbose ); } + let path_info = path.map(|p| format!("\t{}", p.to_string_lossy())).unwrap_or_default(); if atty::is(atty::Stream::Stdout) { - println!( - "{} ({:} bytes)", - size.file_size(size_format).unwrap(), - size.to_formatted_string(&Locale::en) - ); + let human_readable_size = size.file_size(size_format).unwrap(); + let size_in_bytes = size.to_formatted_string(&Locale::en); + if verbose { + format!("{} ({:} bytes){}", human_readable_size, size_in_bytes, path_info) + } else { + format!("{}{}", human_readable_size, path_info) + } } else { - println!("{}", size); + format!("{}{}", size, path_info) } } @@ -54,16 +59,21 @@ fn perform_walks(walks: Vec, aggregate: bool, size_format: FileSizeOpts, v all_errors.extend(errors); } - print_result(total_size, &all_errors, &size_format, verbose); + println!("{}", + build_message(None, total_size, &all_errors, &size_format, verbose) + ); } else { + let mut tw = TabWriter::new(io::stdout()).padding(2); for walk in walks { // each Walk knows its own root_directories let (size, errors) = walk.run(); - for path in walk.get_root_directories() { - println!("{}:", path.display()); - } - print_result(size, &errors, &size_format, verbose); + assert_eq!(walk.get_root_directories().len(), 1, "perform_walks can only be called without aggregation with a single root directory"); + let path = &walk.get_root_directories()[0]; + writeln!(tw, "{}", + build_message(Some(path), size, &errors, &size_format, verbose) + ).unwrap(); } + tw.flush().unwrap(); } } diff --git a/tests/aggregate.rs b/tests/aggregate.rs index 779a6d8..2611504 100644 --- a/tests/aggregate.rs +++ b/tests/aggregate.rs @@ -28,9 +28,9 @@ fn test_diskus_output_lines() { let out_str = String::from_utf8_lossy(&output); // Each path prints one header line + one size line - // e.g., "dir1:\n4196 bytes\n" + // e.g., "4196\tdir1" let lines: Vec<&str> = out_str.lines().collect(); - assert_eq!(lines.len(), 4); // 2 directories × 2 lines each (header + size) + assert_eq!(lines.len(), 2); // ---- Aggregate mode (-a) ---- let mut cmd = Command::cargo_bin("diskus").unwrap(); From 628d4d5edf331a35ceb7f06298a2e6d58d56a1c2 Mon Sep 17 00:00:00 2001 From: Luzian Hahn Date: Tue, 26 Aug 2025 14:28:45 +0200 Subject: [PATCH 3/5] chore: bump to 0.9.0 with docs --- CHANGELOG.md | 6 ++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- doc/diskus.1 | 3 +++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d90c94d..788ddde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,12 @@ ## Packaging +# v0.9.0 + +The default behavior for `diskus` changed, when multiple paths are passed as arguments. +Beforehand, there size was aggregated. Now `diskus` returns the sizes for each path separately. +One can still tigger aggregation via `-a/--aggregate`. + # v0.6.0 - Updated dependencies diff --git a/Cargo.lock b/Cargo.lock index c0161da..2af8cb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,7 +152,7 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" [[package]] name = "diskus" -version = "0.8.0" +version = "0.9.0" dependencies = [ "assert_cmd", "atty", diff --git a/Cargo.toml b/Cargo.toml index f41aa5c..e951ac6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT/Apache-2.0" name = "diskus" readme = "README.md" repository = "https://github.com/sharkdp/diskus" -version = "0.8.0" +version = "0.9.0" edition = "2021" rust-version = "1.76" diff --git a/doc/diskus.1 b/doc/diskus.1 index 8ffc5c7..0004c61 100644 --- a/doc/diskus.1 +++ b/doc/diskus.1 @@ -17,6 +17,9 @@ Output format for file sizes (decimal: MB, binary: MiB) [default: decimal] \fB\-v\fR, \fB\-\-verbose\fR Do not hide filesystem errors .TP +\fB\-a\fR, \fB\-\-aggregate\fR +Aggregate sizes of all passed filesystem paths +.TP \fB\-b\fR, \fB\-\-apparent\-size\fR Compute apparent size instead of disk usage .TP From 67019dbe4308a8e5312b8c767444b51486b64774 Mon Sep 17 00:00:00 2001 From: Luzian Hahn Date: Mon, 10 Nov 2025 10:04:04 +0100 Subject: [PATCH 4/5] feat: create explicit flag to toggle human-readable content before this was triggered when piping into files, but also in pipes in general, which makes things like "grep" difficult --- src/main.rs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index 299a0e3..d7913a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use tabwriter::TabWriter; use diskus::{Error, FilesizeType, Walk}; -fn build_message(path: Option<&PathBuf>, size: u64, errors: &[Error], size_format: &FileSizeOpts, verbose: bool) -> String { +fn build_message(path: Option<&PathBuf>, size: u64, errors: &[Error], size_format: &FileSizeOpts, raw: bool, verbose: bool) -> String { if verbose { for err in errors { match err { @@ -34,7 +34,9 @@ fn build_message(path: Option<&PathBuf>, size: u64, errors: &[Error], size_forma } let path_info = path.map(|p| format!("\t{}", p.to_string_lossy())).unwrap_or_default(); - if atty::is(atty::Stream::Stdout) { + if raw { + format!("{}{}", size, path_info) + } else { let human_readable_size = size.file_size(size_format).unwrap(); let size_in_bytes = size.to_formatted_string(&Locale::en); if verbose { @@ -42,13 +44,11 @@ fn build_message(path: Option<&PathBuf>, size: u64, errors: &[Error], size_forma } else { format!("{}{}", human_readable_size, path_info) } - } else { - format!("{}{}", size, path_info) } } -fn perform_walks(walks: Vec, aggregate: bool, size_format: FileSizeOpts, verbose: bool) { +fn perform_walks(walks: Vec, aggregate: bool, size_format: FileSizeOpts, raw: bool, verbose: bool) { if aggregate { let mut total_size = 0; let mut all_errors = Vec::new(); @@ -60,7 +60,7 @@ fn perform_walks(walks: Vec, aggregate: bool, size_format: FileSizeOpts, v } println!("{}", - build_message(None, total_size, &all_errors, &size_format, verbose) + build_message(None, total_size, &all_errors, &size_format, raw, verbose) ); } else { let mut tw = TabWriter::new(io::stdout()).padding(2); @@ -70,7 +70,7 @@ fn perform_walks(walks: Vec, aggregate: bool, size_format: FileSizeOpts, v assert_eq!(walk.get_root_directories().len(), 1, "perform_walks can only be called without aggregation with a single root directory"); let path = &walk.get_root_directories()[0]; writeln!(tw, "{}", - build_message(Some(path), size, &errors, &size_format, verbose) + build_message(Some(path), size, &errors, &size_format, raw, verbose) ).unwrap(); } tw.flush().unwrap(); @@ -107,6 +107,12 @@ fn main() { .default_value("decimal") .help("Output format for file sizes (decimal: MB, binary: MiB)"), ) + .arg( + Arg::with_name("raw") + .long("raw") + .takes_value(false) + .help("Instead of human-readable sizes uses raw numbers in bytes. Makes the system ignore the parameter \"size-format\"."), + ) .arg( Arg::with_name("verbose") .long("verbose") @@ -158,6 +164,7 @@ fn main() { _ => file_size_opts::BINARY, }; + let raw = matches.is_present("raw"); let verbose = matches.is_present("verbose"); let aggregate = matches.is_present("aggregate"); let walks: Vec = if aggregate { @@ -169,5 +176,5 @@ fn main() { .collect() }; - perform_walks(walks, aggregate, size_format, verbose); + perform_walks(walks, aggregate, size_format, raw, verbose); } From 5e20ab9ca5d2d8bf09a25ee2bf51f69539796ff9 Mon Sep 17 00:00:00 2001 From: Luzian Hahn Date: Mon, 10 Nov 2025 10:21:25 +0100 Subject: [PATCH 5/5] chore: bump to 0.10.0 with docs --- CHANGELOG.md | 15 ++++++++++++--- Cargo.toml | 2 +- doc/diskus.1 | 3 +++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 788ddde..1da2fcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,11 +14,20 @@ ## Packaging +# v0.10.0 + +## Features + +- `diskus` was per default changing its behavior, if it is writing to `stdout`. This might be working well, when writing to files, but causes implicit problems, when piping e.g. into `grep` to determine large elements in a `diskus` output. + This is behavior is now made configurable via `--raw` flag and per default off. + # v0.9.0 -The default behavior for `diskus` changed, when multiple paths are passed as arguments. -Beforehand, there size was aggregated. Now `diskus` returns the sizes for each path separately. -One can still tigger aggregation via `-a/--aggregate`. +## Features + +- The default behavior for `diskus` changed, when multiple paths are passed as arguments. + Beforehand, there size was aggregated. Now `diskus` returns the sizes for each path separately. + One can still tigger aggregation via `-a/--aggregate`. # v0.6.0 diff --git a/Cargo.toml b/Cargo.toml index e951ac6..1569db1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT/Apache-2.0" name = "diskus" readme = "README.md" repository = "https://github.com/sharkdp/diskus" -version = "0.9.0" +version = "0.10.0" edition = "2021" rust-version = "1.76" diff --git a/doc/diskus.1 b/doc/diskus.1 index 0004c61..c8812be 100644 --- a/doc/diskus.1 +++ b/doc/diskus.1 @@ -14,6 +14,9 @@ Set the number of threads (default: 3 x num cores) Output format for file sizes (decimal: MB, binary: MiB) [default: decimal] [possible values: decimal, binary] .TP +\fB\-v\fR, \fB\-\-raw\fR +Output raw numbers of bytes and do not apply any formatting via \fB\-\-size\-format\fR. +.TP \fB\-v\fR, \fB\-\-verbose\fR Do not hide filesystem errors .TP