diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d0118bab..2d84d124c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Add IEC and SI units and unit prefixes to `--size` - In keeping with the coreutils change, add quotes and escapes for necessary filenames from [merelymyself](https://github.com/merelymyself) - Add support for icon theme from [zwpaper](https://github.com/zwpaper) - Add icon for kt and kts from [LeeWeeder](https://github.com/LeeWeeder) diff --git a/README.md b/README.md index cd6d1de30..d2554281d 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,7 @@ recursion: # == Size == # Specifies the format of the size column. -# Possible values: default, short, bytes +# Possible values: default, short, iec, si, bytes size: default # == Permission == diff --git a/doc/lsd.md b/doc/lsd.md index 1a3cc3ed1..9d334b658 100644 --- a/doc/lsd.md +++ b/doc/lsd.md @@ -123,7 +123,7 @@ lsd is a ls command with a lot of pretty colours and some other stuff to enrich : How to display permissions [default: rwx] [possible values: rwx, octal] `--size ...` -: How to display size [default: default] [possible values: default, short, bytes] +: How to display size [default: default] [possible values: default, short, iec, si, bytes] `--sort ...` : Sort by WORD instead of name [possible values: size, time, version, extension] diff --git a/src/app.rs b/src/app.rs index 340abb1b5..bb6e78e8d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -56,6 +56,10 @@ pub struct Cli { #[arg(short, long)] human_readable: bool, + /// Shows size using SI units (powers of 1000) + #[arg(long)] + pub si: bool, + /// Recurse into directories and present the result as a tree #[arg(long)] pub tree: bool, @@ -73,7 +77,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", "iec", "si", "bytes"])] pub size: Option, /// Display the total size of directories diff --git a/src/config_file.rs b/src/config_file.rs index d2c6d7c95..c12c804f9 100644 --- a/src/config_file.rs +++ b/src/config_file.rs @@ -284,7 +284,7 @@ recursion: # == Size == # Specifies the format of the size column. -# Possible values: default, short, bytes +# Possible values: default, short, iec, si, bytes size: default # == Permission == diff --git a/src/flags/size.rs b/src/flags/size.rs index 4a116cc50..b5e33092b 100644 --- a/src/flags/size.rs +++ b/src/flags/size.rs @@ -17,6 +17,10 @@ pub enum SizeFlag { Default, /// The variant to show file size with only the SI unit prefix. Short, + /// The variant to show file size with IEC unit prefix and a B for bytes + Iec, + /// The variant to show file size with SI units and SI unit prefixes and a B for bytes + Si, /// The variant to show file size in bytes. Bytes, } @@ -26,6 +30,8 @@ impl SizeFlag { match value { "default" => Self::Default, "short" => Self::Short, + "iec" => Self::Iec, + "si" => Self::Si, "bytes" => Self::Bytes, // Invalid value should be handled by `clap` when building an `Cli` other => unreachable!("Invalid value '{other}' for 'size'"), @@ -36,14 +42,18 @@ impl SizeFlag { impl Configurable for SizeFlag { /// Get a potential `SizeFlag` variant from [Cli]. /// - /// If any of the "default", "short" or "bytes" arguments is passed, the corresponding - /// `SizeFlag` variant is returned in a [Some]. If neither of them is passed, this returns - /// [None]. + /// If any of the "default", "short", "iec", "si", or "bytes" arguments is passed, + /// the corresponding `SizeFlag` variant is returned in a [Some]. If neither of them is passed, + /// this returns [None]. fn from_cli(cli: &Cli) -> Option { if cli.classic { Some(Self::Bytes) } else { - cli.size.as_deref().map(Self::from_arg_str) + if cli.si { + Some(Self::Si) + } else { + cli.size.as_deref().map(Self::from_arg_str) + } } } @@ -97,6 +107,20 @@ mod test { assert_eq!(Some(SizeFlag::Short), SizeFlag::from_cli(&cli)); } + #[test] + fn test_from_cli_iec() { + let argv = ["lsd", "--size", "iec"]; + let cli = Cli::try_parse_from(argv).unwrap(); + assert_eq!(Some(SizeFlag::Iec), SizeFlag::from_cli(&cli)); + } + + #[test] + fn test_from_cli_si() { + let argv = ["lsd", "--size", "si"]; + let cli = Cli::try_parse_from(argv).unwrap(); + assert_eq!(Some(SizeFlag::Si), SizeFlag::from_cli(&cli)); + } + #[test] fn test_from_cli_bytes() { let argv = ["lsd", "--size", "bytes"]; @@ -143,6 +167,20 @@ mod test { assert_eq!(Some(SizeFlag::Short), SizeFlag::from_config(&c)); } + #[test] + fn test_from_config_iec() { + let mut c = Config::with_none(); + c.size = Some(SizeFlag::Iec); + assert_eq!(Some(SizeFlag::Iec), SizeFlag::from_config(&c)); + } + + #[test] + fn test_from_config_si() { + let mut c = Config::with_none(); + c.size = Some(SizeFlag::Si); + assert_eq!(Some(SizeFlag::Si), SizeFlag::from_config(&c)); + } + #[test] fn test_from_config_bytes() { let mut c = Config::with_none(); diff --git a/src/meta/size.rs b/src/meta/size.rs index 9f39b993c..9aea6e1b3 100644 --- a/src/meta/size.rs +++ b/src/meta/size.rs @@ -2,10 +2,15 @@ use crate::color::{ColoredString, Colors, Elem}; use crate::flags::{Flags, SizeFlag}; use std::fs::Metadata; -const KB: u64 = 1024; -const MB: u64 = 1024_u64.pow(2); -const GB: u64 = 1024_u64.pow(3); -const TB: u64 = 1024_u64.pow(4); +// IEC constants are capitalized to follow Rust naming style +const KIB: u64 = 1024; +const MIB: u64 = 1024_u64.pow(2); +const GIB: u64 = 1024_u64.pow(3); +const TIB: u64 = 1024_u64.pow(4); +const KB: u64 = 1000; +const MB: u64 = 1000_u64.pow(2); +const GB: u64 = 1000_u64.pow(3); +const TB: u64 = 1000_u64.pow(4); #[derive(Clone, Debug, PartialEq, Eq)] pub enum Unit { @@ -46,10 +51,10 @@ impl Size { } match self.bytes { - b if b < KB => Unit::Byte, - b if b < MB => Unit::Kilo, - b if b < GB => Unit::Mega, - b if b < TB => Unit::Giga, + b if b < KIB => Unit::Byte, + b if b < MIB => Unit::Kilo, + b if b < GIB => Unit::Mega, + b if b < TIB => Unit::Giga, _ => Unit::Tera, } } @@ -106,12 +111,37 @@ impl Size { pub fn value_string(&self, flags: &Flags) -> String { let unit = self.get_unit(flags); - match unit { - Unit::Byte => self.bytes.to_string(), - 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), - Unit::Tera => self.format_size(((self.bytes as f64 / TB as f64) * 10.0).round() / 10.0), + match flags.size { + SizeFlag::Si => match unit { + Unit::Byte => self.bytes.to_string(), + 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) + } + Unit::Tera => { + self.format_size(((self.bytes as f64 / TB as f64) * 10.0).round() / 10.0) + } + }, + _ => match unit { + Unit::Byte => self.bytes.to_string(), + Unit::Kilo => { + self.format_size(((self.bytes as f64 / KIB as f64) * 10.0).round() / 10.0) + } + Unit::Mega => { + self.format_size(((self.bytes as f64 / MIB as f64) * 10.0).round() / 10.0) + } + Unit::Giga => { + self.format_size(((self.bytes as f64 / GIB as f64) * 10.0).round() / 10.0) + } + Unit::Tera => { + self.format_size(((self.bytes as f64 / TIB as f64) * 10.0).round() / 10.0) + } + }, } } @@ -125,13 +155,6 @@ impl Size { let unit = self.get_unit(flags); match flags.size { - SizeFlag::Default => match unit { - Unit::Byte => String::from('B'), - Unit::Kilo => String::from("KB"), - Unit::Mega => String::from("MB"), - Unit::Giga => String::from("GB"), - Unit::Tera => String::from("TB"), - }, SizeFlag::Short => match unit { Unit::Byte => String::from('B'), Unit::Kilo => String::from('K'), @@ -139,14 +162,28 @@ impl Size { Unit::Giga => String::from('G'), Unit::Tera => String::from('T'), }, + SizeFlag::Iec => match unit { + Unit::Byte => String::from('B'), + Unit::Kilo => String::from("KiB"), + Unit::Mega => String::from("MiB"), + Unit::Giga => String::from("GiB"), + Unit::Tera => String::from("TiB"), + }, SizeFlag::Bytes => String::from(""), + _ => match unit { + Unit::Byte => String::from('B'), + Unit::Kilo => String::from("KB"), + Unit::Mega => String::from("MB"), + Unit::Giga => String::from("GB"), + Unit::Tera => String::from("TB"), + }, } } } #[cfg(test)] mod test { - use super::{Size, GB, KB, MB, TB}; + use super::{Size, GIB, KIB, MIB, TIB}; use crate::color::{Colors, ThemeOption}; use crate::flags::{Flags, SizeFlag}; @@ -166,139 +203,187 @@ mod test { #[test] fn render_10_minus_kilobyte() { - let size = Size::new(4 * KB); // 4 kilobytes + let size = Size::new(4 * KIB); // 4 kilobytes let mut flags = Flags::default(); assert_eq!(size.value_string(&flags), "4.0"); assert_eq!(size.unit_string(&flags), "KB"); flags.size = SizeFlag::Short; assert_eq!(size.unit_string(&flags), "K"); + flags.size = SizeFlag::Iec; + assert_eq!(size.unit_string(&flags), "KiB"); + flags.size = SizeFlag::Si; + assert_eq!(size.unit_string(&flags), "KB"); } #[test] fn render_kilobyte() { - let size = Size::new(42 * KB); // 42 kilobytes + let size = Size::new(42 * KIB); // 42 kilobytes let mut flags = Flags::default(); assert_eq!(size.value_string(&flags), "42"); assert_eq!(size.unit_string(&flags), "KB"); flags.size = SizeFlag::Short; assert_eq!(size.unit_string(&flags), "K"); + flags.size = SizeFlag::Iec; + assert_eq!(size.unit_string(&flags), "KiB"); + flags.size = SizeFlag::Si; + assert_eq!(size.unit_string(&flags), "KB"); } #[test] fn render_100_plus_kilobyte() { - let size = Size::new(420 * KB + 420); // 420.4 kilobytes + let size = Size::new(420 * KIB + 420); // 420.4 kilobytes let mut flags = Flags::default(); assert_eq!(size.value_string(&flags), "420"); assert_eq!(size.unit_string(&flags), "KB"); flags.size = SizeFlag::Short; assert_eq!(size.unit_string(&flags), "K"); + flags.size = SizeFlag::Iec; + assert_eq!(size.unit_string(&flags), "KiB"); + flags.size = SizeFlag::Si; + assert_eq!(size.unit_string(&flags), "KB"); } #[test] fn render_10_minus_megabyte() { - let size = Size::new(4 * MB); // 4 megabytes + let size = Size::new(4 * MIB); // 4 megabytes let mut flags = Flags::default(); assert_eq!(size.value_string(&flags), "4.0"); assert_eq!(size.unit_string(&flags), "MB"); flags.size = SizeFlag::Short; assert_eq!(size.unit_string(&flags), "M"); + flags.size = SizeFlag::Iec; + assert_eq!(size.unit_string(&flags), "MiB"); + flags.size = SizeFlag::Si; + assert_eq!(size.unit_string(&flags), "MB"); } #[test] fn render_megabyte() { - let size = Size::new(42 * MB); // 42 megabytes + let size = Size::new(42 * MIB); // 42 megabytes let mut flags = Flags::default(); assert_eq!(size.value_string(&flags), "42"); assert_eq!(size.unit_string(&flags), "MB"); flags.size = SizeFlag::Short; assert_eq!(size.unit_string(&flags), "M"); + flags.size = SizeFlag::Iec; + assert_eq!(size.unit_string(&flags), "MiB"); + flags.size = SizeFlag::Si; + assert_eq!(size.unit_string(&flags), "MB"); } #[test] fn render_100_plus_megabyte() { - let size = Size::new(420 * MB + 420 * KB); // 420.4 megabytes + let size = Size::new(420 * MIB + 420 * KIB); // 420.4 megabytes let mut flags = Flags::default(); assert_eq!(size.value_string(&flags), "420"); assert_eq!(size.unit_string(&flags), "MB"); flags.size = SizeFlag::Short; assert_eq!(size.unit_string(&flags), "M"); + flags.size = SizeFlag::Iec; + assert_eq!(size.unit_string(&flags), "MiB"); + flags.size = SizeFlag::Si; + assert_eq!(size.unit_string(&flags), "MB"); } #[test] fn render_10_minus_gigabyte() { - let size = Size::new(4 * GB); // 4 gigabytes + let size = Size::new(4 * GIB); // 4 gigabytes let mut flags = Flags::default(); assert_eq!(size.value_string(&flags), "4.0"); assert_eq!(size.unit_string(&flags), "GB"); flags.size = SizeFlag::Short; assert_eq!(size.unit_string(&flags), "G"); + flags.size = SizeFlag::Iec; + assert_eq!(size.unit_string(&flags), "GiB"); + flags.size = SizeFlag::Si; + assert_eq!(size.unit_string(&flags), "GB"); } #[test] fn render_gigabyte() { - let size = Size::new(42 * GB); // 42 gigabytes + let size = Size::new(42 * GIB); // 42 gigabytes let mut flags = Flags::default(); assert_eq!(size.value_string(&flags), "42"); assert_eq!(size.unit_string(&flags), "GB"); flags.size = SizeFlag::Short; assert_eq!(size.unit_string(&flags), "G"); + flags.size = SizeFlag::Iec; + assert_eq!(size.unit_string(&flags), "GiB"); + flags.size = SizeFlag::Si; + assert_eq!(size.unit_string(&flags), "GB"); } #[test] fn render_100_plus_gigabyte() { - let size = Size::new(420 * GB + 420 * MB); // 420.4 gigabytes + let size = Size::new(420 * GIB + 420 * MIB); // 420.4 gigabytes let mut flags = Flags::default(); assert_eq!(size.value_string(&flags), "420"); assert_eq!(size.unit_string(&flags), "GB"); flags.size = SizeFlag::Short; assert_eq!(size.unit_string(&flags), "G"); + flags.size = SizeFlag::Iec; + assert_eq!(size.unit_string(&flags), "GiB"); + flags.size = SizeFlag::Si; + assert_eq!(size.unit_string(&flags), "GB"); } #[test] fn render_10_minus_terabyte() { - let size = Size::new(4 * TB); // 4 terabytes + let size = Size::new(4 * TIB); // 4 terabytes let mut flags = Flags::default(); assert_eq!(size.value_string(&flags), "4.0"); assert_eq!(size.unit_string(&flags), "TB"); flags.size = SizeFlag::Short; assert_eq!(size.unit_string(&flags), "T"); + flags.size = SizeFlag::Iec; + assert_eq!(size.unit_string(&flags), "TiB"); + flags.size = SizeFlag::Si; + assert_eq!(size.unit_string(&flags), "TB"); } #[test] fn render_terabyte() { - let size = Size::new(42 * TB); // 42 terabytes + let size = Size::new(42 * TIB); // 42 terabytes let mut flags = Flags::default(); assert_eq!(size.value_string(&flags), "42"); assert_eq!(size.unit_string(&flags), "TB"); flags.size = SizeFlag::Short; assert_eq!(size.unit_string(&flags), "T"); + flags.size = SizeFlag::Iec; + assert_eq!(size.unit_string(&flags), "TiB"); + flags.size = SizeFlag::Si; + assert_eq!(size.unit_string(&flags), "TB"); } #[test] fn render_100_plus_terabyte() { - let size = Size::new(420 * TB + 420 * GB); // 420.4 terabytes + let size = Size::new(420 * TIB + 420 * GIB); // 420.4 terabytes let mut flags = Flags::default(); assert_eq!(size.value_string(&flags), "420"); assert_eq!(size.unit_string(&flags), "TB"); flags.size = SizeFlag::Short; assert_eq!(size.unit_string(&flags), "T"); + flags.size = SizeFlag::Iec; + assert_eq!(size.unit_string(&flags), "TiB"); + flags.size = SizeFlag::Si; + assert_eq!(size.unit_string(&flags), "TB"); } #[test] fn render_with_a_fraction() { - let size = Size::new(42 * KB + 103); // 42.1 kilobytes + let size = Size::new(42 * KIB + 103); // 42.1 kilobytes let flags = Flags::default(); assert_eq!(size.value_string(&flags), "42"); @@ -307,7 +392,7 @@ mod test { #[test] fn render_with_a_truncated_fraction() { - let size = Size::new(42 * KB + 1); // 42.001 kilobytes == 42 kilobytes + let size = Size::new(42 * KIB + 1); // 42.001 kilobytes == 42 kilobytes let flags = Flags::default(); assert_eq!(size.value_string(&flags), "42"); @@ -316,7 +401,7 @@ mod test { #[test] fn render_short_nospaces() { - let size = Size::new(42 * KB); // 42 kilobytes + let size = Size::new(42 * KIB); // 42 kilobytes let flags = Flags { size: SizeFlag::Short, ..Default::default()