diff --git a/Cargo.lock b/Cargo.lock index 90eee98719..bc7d11b18c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,6 +103,15 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + [[package]] name = "arrayvec" version = "0.5.2" @@ -269,7 +278,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" dependencies = [ "arrayref", - "arrayvec", + "arrayvec 0.5.2", "constant_time_eq", ] @@ -306,6 +315,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bumpalo" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" + [[package]] name = "byte-unit" version = "4.0.13" @@ -548,6 +563,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "cstr_core" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ba9efe9e1e736671d5a03f006afc4e7e3f32503e2077e0bcaf519c0c8c1d3" +dependencies = [ + "cty", + "memchr", +] + [[package]] name = "csv" version = "1.1.6" @@ -590,6 +615,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + [[package]] name = "dialoguer" version = "0.9.0" @@ -1137,6 +1168,15 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1455,6 +1495,12 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + [[package]] name = "nom" version = "1.2.4" @@ -1631,8 +1677,10 @@ dependencies = [ "indexmap", "miette", "nu-json", + "num-format", "serde", "serde_json", + "sys-locale", "thiserror", "typetag", ] @@ -1754,6 +1802,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-format" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bafe4179722c2894288ee77a9f044f02811c86af699344c498b0840c698a2465" +dependencies = [ + "arrayvec 0.4.12", + "itoa", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -2660,6 +2718,19 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "sys-locale" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91f89ebb59fa30d4f65fafc2d68e94f6975256fd87e812dd99cb6e020c8563df" +dependencies = [ + "cc", + "cstr_core", + "libc", + "web-sys", + "winapi", +] + [[package]] name = "sysinfo" version = "0.20.5" @@ -2967,7 +3038,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" dependencies = [ - "arrayvec", + "arrayvec 0.5.2", "utf8parse", "vte_generate_state_changes", ] @@ -3003,6 +3074,70 @@ version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +[[package]] +name = "wasm-bindgen" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" + +[[package]] +name = "web-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index 179518b350..51749f52c2 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -17,6 +17,8 @@ im = "15.0.0" serde_json = { version = "1.0", optional = true } nu-json = { path = "../nu-json" } typetag = "0.1.8" +num-format = "0.4.0" +sys-locale = "0.1.0" [features] plugin = ["serde_json"] diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index 4635d90eec..11f67af923 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -14,6 +14,7 @@ pub struct Config { pub footer_mode: FooterMode, pub animate_prompt: bool, pub float_precision: i64, + pub filesize_format: String, pub without_color: bool, } @@ -28,6 +29,7 @@ impl Default for Config { footer_mode: FooterMode::Never, animate_prompt: ANIMATE_PROMPT_DEFAULT, float_precision: 4, + filesize_format: "auto".into(), without_color: false, } } @@ -74,7 +76,7 @@ impl Value { config.use_grid_icons = value.as_bool()?; } "footer_mode" => { - let val_str = value.as_string()?; + let val_str = value.as_string()?.to_lowercase(); config.footer_mode = match val_str.as_ref() { "auto" => FooterMode::Auto, "never" => FooterMode::Never, @@ -94,6 +96,9 @@ impl Value { "without_color" => { config.without_color = value.as_bool()?; } + "filesize_format" => { + config.filesize_format = value.as_string()?.to_lowercase(); + } _ => {} } } diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 6abaf6e151..a64ac7e20a 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -5,13 +5,16 @@ mod range; mod stream; mod unit; +use byte_unit::ByteUnit; use chrono::{DateTime, FixedOffset}; use chrono_humanize::HumanTime; pub use from_value::FromValue; use indexmap::map::IndexMap; +use num_format::{Locale, ToFormattedString}; pub use range::*; use serde::{Deserialize, Serialize}; pub use stream::*; +use sys_locale::get_locale; pub use unit::*; use std::collections::HashMap; @@ -1474,16 +1477,114 @@ pub fn format_duration(duration: i64) -> String { } fn format_filesize(num_bytes: i64, config: &Config) -> String { + // Allow the user to specify how they want their numbers formatted + let filesize_format_var = get_config_filesize_format(config); + let byte = byte_unit::Byte::from_bytes(num_bytes as u128); + let adj_byte = + if filesize_format_var.0 == byte_unit::ByteUnit::B && filesize_format_var.1 == "auto" { + byte.get_appropriate_unit(!config.filesize_metric) + } else { + byte.get_adjusted_unit(filesize_format_var.0) + }; - if byte.get_bytes() == 0u128 { - return "—".to_string(); - } + match adj_byte.get_unit() { + byte_unit::ByteUnit::B => { + let locale_string = get_locale().unwrap_or_else(|| String::from("en-US")); + // Since get_locale() and Locale::from_name() don't always return the same items + // we need to try and parse it to match. For instance, a valid locale is de_DE + // however Locale::from_name() wants only de so we split and parse it out. + let locale_string = locale_string.replace("_", "-"); // en_AU -> en-AU + let locale = match Locale::from_name(&locale_string) { + Ok(loc) => loc, + _ => { + let all = num_format::Locale::available_names(); + let locale_prefix = &locale_string.split('-').collect::>(); + if all.contains(&locale_prefix[0]) { + // eprintln!("Found alternate: {}", &locale_prefix[0]); + Locale::from_name(locale_prefix[0]).unwrap_or(Locale::en) + } else { + // eprintln!("Unable to find matching locale. Defaulting to en-US"); + Locale::en + } + } + }; + let locale_byte = adj_byte.get_value() as u64; + let locale_byte_string = locale_byte.to_formatted_string(&locale); - let byte = byte.get_appropriate_unit(config.filesize_metric); - - match byte.get_unit() { - byte_unit::ByteUnit::B => format!("{} B ", byte.get_value()), - _ => byte.format(1), + if filesize_format_var.1 == "auto" { + format!("{} B", locale_byte_string) + } else { + locale_byte_string + } + } + _ => adj_byte.format(1), } } + +fn get_config_filesize_format(config: &Config) -> (ByteUnit, &str) { + // We need to take into account config.filesize_metric so, if someone asks for KB + // filesize_metric is true, return KiB + let filesize_format = match config.filesize_format.as_str() { + "b" => (byte_unit::ByteUnit::B, ""), + "kb" => { + if config.filesize_metric { + (byte_unit::ByteUnit::KiB, "") + } else { + (byte_unit::ByteUnit::KB, "") + } + } + "kib" => (byte_unit::ByteUnit::KiB, ""), + "mb" => { + if config.filesize_metric { + (byte_unit::ByteUnit::MiB, "") + } else { + (byte_unit::ByteUnit::MB, "") + } + } + "mib" => (byte_unit::ByteUnit::MiB, ""), + "gb" => { + if config.filesize_metric { + (byte_unit::ByteUnit::GiB, "") + } else { + (byte_unit::ByteUnit::GB, "") + } + } + "gib" => (byte_unit::ByteUnit::GiB, ""), + "tb" => { + if config.filesize_metric { + (byte_unit::ByteUnit::TiB, "") + } else { + (byte_unit::ByteUnit::TB, "") + } + } + "tib" => (byte_unit::ByteUnit::TiB, ""), + "pb" => { + if config.filesize_metric { + (byte_unit::ByteUnit::PiB, "") + } else { + (byte_unit::ByteUnit::PB, "") + } + } + "pib" => (byte_unit::ByteUnit::PiB, ""), + "eb" => { + if config.filesize_metric { + (byte_unit::ByteUnit::EiB, "") + } else { + (byte_unit::ByteUnit::EB, "") + } + } + "eib" => (byte_unit::ByteUnit::EiB, ""), + "zb" => { + if config.filesize_metric { + (byte_unit::ByteUnit::ZiB, "") + } else { + (byte_unit::ByteUnit::ZB, "") + } + } + "zib" => (byte_unit::ByteUnit::ZiB, ""), + _ => (byte_unit::ByteUnit::B, "auto"), + }; + + filesize_format +} diff --git a/src/tests.rs b/src/tests.rs index 4b9754b00a..f8be6382da 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1148,19 +1148,28 @@ fn multi_word_imports() -> TestResult { } #[test] -fn config_var_1() -> TestResult { +fn config_filesize_format_with_metric_true() -> TestResult { // Note: this tests both the config variable and that it is properly captured into a block run_test( - r#"let config = {"filesize_metric": $true }; do { 40kb | into string } "#, + r#"let config = {"filesize_metric": $true "filesize_format": "kib" }; do { 40kb | into string } "#, "39.1 KiB", ) } #[test] -fn config_var_2() -> TestResult { +fn config_filesize_format_with_metric_false_kib() -> TestResult { // Note: this tests both the config variable and that it is properly captured into a block run_test( - r#"let config = {"filesize_metric": $false }; do { 40kb | into string } "#, + r#"let config = {"filesize_metric": $false "filesize_format": "kib" }; do { 40kb | into string } "#, + "39.1 KiB", + ) +} + +#[test] +fn config_filesize_format_with_metric_false_kb() -> TestResult { + // Note: this tests both the config variable and that it is properly captured into a block + run_test( + r#"let config = {"filesize_metric": $false "filesize_format": "kb" }; do { 40kb | into string } "#, "40.0 KB", ) }