From a3b3dea06cdb0c01ecf6ea416cf63d7af20a28ed Mon Sep 17 00:00:00 2001 From: Fotis Gimian Date: Sun, 29 Dec 2024 21:27:32 +1100 Subject: [PATCH] Add support for displaying file sizes in bytes with a separator --- Cargo.lock | 190 ++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 2 + doc/lsd.md | 2 +- src/app.rs | 2 +- src/flags/size.rs | 10 +++ src/meta/size.rs | 60 ++++++++++++++- 6 files changed, 255 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab12a299c..d95c2dcb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,6 +75,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[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.14" @@ -111,6 +117,28 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +[[package]] +name = "bindgen" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36d860121800b2a9a94f9b5604b332d5cffb234ce17609ea479d723dbc9d3885" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 1.0.109", + "which", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -153,6 +181,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -183,6 +220,17 @@ dependencies = [ "chrono", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.3.24" @@ -225,7 +273,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.57", ] [[package]] @@ -343,6 +391,21 @@ 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 = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -532,6 +595,15 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "human-sort" version = "0.2.2" @@ -649,6 +721,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.153" @@ -667,6 +745,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets 0.48.5", +] + [[package]] name = "libredox" version = "0.1.3" @@ -749,6 +837,7 @@ dependencies = [ "human-sort", "libc", "lscolors", + "num-format", "once_cell", "predicates", "serde", @@ -777,6 +866,12 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "mio" version = "0.8.11" @@ -789,6 +884,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -804,6 +909,32 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec", + "cfg-if", + "encoding_rs", + "itoa", + "lazy_static", + "libc", + "num-format-windows", + "widestring", + "winapi", +] + +[[package]] +name = "num-format-windows" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b1e07f67225c1eb911d16c2f72492669c1fb08212dbfaa1f6cfbeb119152cfa" +dependencies = [ + "bindgen", +] + [[package]] name = "num-traits" version = "0.2.18" @@ -848,6 +979,12 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -975,6 +1112,12 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustix" version = "0.37.27" @@ -1040,7 +1183,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.57", ] [[package]] @@ -1078,9 +1221,15 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.57", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook" version = "0.3.17" @@ -1132,6 +1281,17 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.57" @@ -1216,7 +1376,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.57", ] [[package]] @@ -1358,7 +1518,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.57", "wasm-bindgen-shared", ] @@ -1380,7 +1540,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.57", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1391,6 +1551,24 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.32", +] + +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + [[package]] name = "wild" version = "2.2.1" diff --git a/Cargo.toml b/Cargo.toml index 731a0db8a..431471950 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,10 +52,12 @@ xdg = "2.5" git2 = { version = "0.18", optional = true, default-features = false } [target.'cfg(unix)'.dependencies] +num-format = { version = "0.4.4", features = ["with-system-locale"] } users = { version = "0.11.3", package = "uzers" } xattr = "1" [target.'cfg(windows)'.dependencies] +num-format = "0.4.4" windows = { version = "0.43.0", features = ["Win32_Foundation", "Win32_Security_Authorization", "Win32_Storage_FileSystem", "Win32_System_Memory"] } [dependencies.clap] diff --git a/doc/lsd.md b/doc/lsd.md index 78a120465..0d7ad26e5 100644 --- a/doc/lsd.md +++ b/doc/lsd.md @@ -126,7 +126,7 @@ lsd is a ls command with a lot of pretty colours and some other stuff to enrich : How to display permissions [default: rwx for linux, attributes for windows] [possible values: rwx, octal, attributes, disable] `--size ...` -: How to display size [default: default] [possible values: default, short, bytes] +: How to display size [default: default] [possible values: default, short, bytes, bytes-with-separator] `--sort ...` : Sort by WORD instead of name [possible values: size, time, version, extension, git] diff --git a/src/app.rs b/src/app.rs index fa329592e..68c95d95a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -73,7 +73,7 @@ pub struct Cli { pub permission: Option, /// How to display size [default: default] - #[arg(long, value_name = "MODE", value_parser = ["default", "short", "bytes"])] + #[arg(long, value_name = "MODE", value_parser = ["default", "short", "bytes", "bytes-with-separator"])] pub size: Option, /// Display the total size of directories diff --git a/src/flags/size.rs b/src/flags/size.rs index 4a116cc50..46117fa57 100644 --- a/src/flags/size.rs +++ b/src/flags/size.rs @@ -19,6 +19,8 @@ pub enum SizeFlag { Short, /// The variant to show file size in bytes. Bytes, + /// The variant to show file size in bytes separated by a suitable delimiter. + BytesWithSeparator, } impl SizeFlag { @@ -27,6 +29,7 @@ impl SizeFlag { "default" => Self::Default, "short" => Self::Short, "bytes" => Self::Bytes, + "bytes-with-separator" => Self::BytesWithSeparator, // Invalid value should be handled by `clap` when building an `Cli` other => unreachable!("Invalid value '{other}' for 'size'"), } @@ -104,6 +107,13 @@ mod test { assert_eq!(Some(SizeFlag::Bytes), SizeFlag::from_cli(&cli)); } + #[test] + fn test_from_cli_bytes_with_separator() { + let argv = ["lsd", "--size", "bytes-with-separator"]; + let cli = Cli::try_parse_from(argv).unwrap(); + assert_eq!(Some(SizeFlag::BytesWithSeparator), SizeFlag::from_cli(&cli)); + } + #[test] #[should_panic] fn test_from_cli_unknown() { diff --git a/src/meta/size.rs b/src/meta/size.rs index 8b66c96e6..035a721b9 100644 --- a/src/meta/size.rs +++ b/src/meta/size.rs @@ -1,5 +1,6 @@ use crate::color::{ColoredString, Colors, Elem}; use crate::flags::{Flags, SizeFlag}; +use num_format::ToFormattedString as _; use std::fs::Metadata; const KB: u64 = 1024; @@ -36,12 +37,34 @@ impl Size { self.bytes } + #[cfg(windows)] + fn format_bytes_with_separator(&self) -> String { + self.bytes.to_formatted_string(&num_format::Locale::en) + } + + #[cfg(not(windows))] + fn format_bytes_with_separator(&self) -> String { + if let Ok(system_locale) = num_format::SystemLocale::default() { + self.bytes.to_formatted_string(&system_locale) + } else { + self.bytes.to_formatted_string(&num_format::Locale::en) + } + } + + fn format_bytes(&self, flags: &Flags) -> String { + if flags.size == SizeFlag::BytesWithSeparator { + self.format_bytes_with_separator() + } else { + self.bytes.to_string() + } + } + fn format_size(&self, number: f64) -> String { format!("{0:.1$}", number, if number < 10.0 { 1 } else { 0 }) } fn get_unit(&self, flags: &Flags) -> Unit { - if flags.size == SizeFlag::Bytes { + if matches!(flags.size, SizeFlag::Bytes | SizeFlag::BytesWithSeparator) { return Unit::Byte; } @@ -110,7 +133,7 @@ impl Size { let unit = self.get_unit(flags); match unit { - Unit::Byte => self.bytes.to_string(), + Unit::Byte => self.format_bytes(flags), Unit::Kilo => self.format_size(((self.bytes as f64 / KB as f64) * 10.0).round() / 10.0), Unit::Mega => self.format_size(((self.bytes as f64 / MB as f64) * 10.0).round() / 10.0), Unit::Giga => self.format_size(((self.bytes as f64 / GB as f64) * 10.0).round() / 10.0), @@ -142,7 +165,7 @@ impl Size { Unit::Giga => String::from('G'), Unit::Tera => String::from('T'), }, - SizeFlag::Bytes => String::from(""), + SizeFlag::Bytes | SizeFlag::BytesWithSeparator => String::from(""), } } } @@ -167,6 +190,37 @@ mod test { assert_eq!(size.unit_string(&flags), ""); } + #[cfg(windows)] + #[test] + fn render_bytes_with_separator() { + let size = Size::new(42 * 1024 * 1024); // 42 megabytes + let mut flags = Flags::default(); + + flags.size = SizeFlag::Bytes; + assert_eq!(size.value_string(&flags).as_str(), "44040192"); + assert_eq!(size.unit_string(&flags).as_str(), ""); + + flags.size = SizeFlag::BytesWithSeparator; + assert_eq!(size.value_string(&flags).as_str(), "44,040,192"); + assert_eq!(size.unit_string(&flags).as_str(), ""); + } + + #[cfg(not(windows))] + #[test] + fn render_bytes_with_separator() { + std::env::set_var("LC_ALL", "en-US.UTF-8"); + let size = Size::new(42 * 1024 * 1024); // 42 megabytes + let mut flags = Flags::default(); + + flags.size = SizeFlag::Bytes; + assert_eq!(size.value_string(&flags).as_str(), "44040192"); + assert_eq!(size.unit_string(&flags).as_str(), ""); + + flags.size = SizeFlag::BytesWithSeparator; + assert_eq!(size.value_string(&flags).as_str(), "44,040,192"); + assert_eq!(size.unit_string(&flags).as_str(), ""); + } + #[test] fn render_10_minus_kilobyte() { let size = Size::new(4 * KB); // 4 kilobytes