From 44e088c6fe9013413766d10c0ed04749045bdca3 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Sat, 30 Jan 2021 11:35:18 +1300 Subject: [PATCH] Move filesize to use bigint (#2984) * Move filesize to be bigint-sized * Add tests and fix filesize display * clippy --- .../src/commands/autoview/command.rs | 7 ++ .../src/commands/format/format_filesize.rs | 118 ++++++++++-------- crates/nu-command/src/commands/math/avg.rs | 13 +- crates/nu-command/src/commands/math/median.rs | 2 +- .../nu-command/src/commands/math/product.rs | 16 +-- crates/nu-command/src/commands/math/sum.rs | 16 +-- .../nu-command/src/commands/math/variance.rs | 2 +- .../src/commands/to_delimited_data.rs | 2 +- crates/nu-command/src/commands/to_toml.rs | 12 +- crates/nu-data/src/base.rs | 12 +- crates/nu-data/src/base/shape.rs | 61 +++++---- crates/nu-data/src/config.rs | 14 ++- crates/nu-data/src/value.rs | 23 ++-- crates/nu-protocol/src/hir.rs | 4 +- crates/nu-protocol/src/value.rs | 4 +- crates/nu-protocol/src/value/primitive.rs | 22 ++-- tests/shell/pipeline/commands/internal.rs | 47 +++++++ 17 files changed, 227 insertions(+), 148 deletions(-) diff --git a/crates/nu-command/src/commands/autoview/command.rs b/crates/nu-command/src/commands/autoview/command.rs index a83d03681f..466414fecf 100644 --- a/crates/nu-command/src/commands/autoview/command.rs +++ b/crates/nu-command/src/commands/autoview/command.rs @@ -173,6 +173,13 @@ pub async fn autoview(context: RunnableContext) -> Result { + let output = format_leaf(&x).plain_string(100_000); + out!("{}", output); + } Value { value: UntaggedValue::Primitive(Primitive::Date(d)), .. diff --git a/crates/nu-command/src/commands/format/format_filesize.rs b/crates/nu-command/src/commands/format/format_filesize.rs index a97186bdad..2b2f0a4dca 100644 --- a/crates/nu-command/src/commands/format/format_filesize.rs +++ b/crates/nu-command/src/commands/format/format_filesize.rs @@ -108,60 +108,70 @@ fn convert_bytes_to_string_using_format( ) -> Result { match bytes.value { Primitive(Filesize(b)) => { - let byte = byte_unit::Byte::from_bytes(b as u128); - let value = match format.item().to_lowercase().as_str() { - "b" => Ok(UntaggedValue::string(b.to_formatted_string(&Locale::en))), - "kb" => Ok(UntaggedValue::string( - byte.get_adjusted_unit(byte_unit::ByteUnit::KB).to_string(), - )), - "kib" => Ok(UntaggedValue::string( - byte.get_adjusted_unit(byte_unit::ByteUnit::KiB).to_string(), - )), - "mb" => Ok(UntaggedValue::string( - byte.get_adjusted_unit(byte_unit::ByteUnit::MB).to_string(), - )), - "mib" => Ok(UntaggedValue::string( - byte.get_adjusted_unit(byte_unit::ByteUnit::MiB).to_string(), - )), - "gb" => Ok(UntaggedValue::string( - byte.get_adjusted_unit(byte_unit::ByteUnit::GB).to_string(), - )), - "gib" => Ok(UntaggedValue::string( - byte.get_adjusted_unit(byte_unit::ByteUnit::GiB).to_string(), - )), - "tb" => Ok(UntaggedValue::string( - byte.get_adjusted_unit(byte_unit::ByteUnit::TB).to_string(), - )), - "tib" => Ok(UntaggedValue::string( - byte.get_adjusted_unit(byte_unit::ByteUnit::TiB).to_string(), - )), - "pb" => Ok(UntaggedValue::string( - byte.get_adjusted_unit(byte_unit::ByteUnit::PB).to_string(), - )), - "pib" => Ok(UntaggedValue::string( - byte.get_adjusted_unit(byte_unit::ByteUnit::PiB).to_string(), - )), - "eb" => Ok(UntaggedValue::string( - byte.get_adjusted_unit(byte_unit::ByteUnit::EB).to_string(), - )), - "eib" => Ok(UntaggedValue::string( - byte.get_adjusted_unit(byte_unit::ByteUnit::EiB).to_string(), - )), - "zb" => Ok(UntaggedValue::string( - byte.get_adjusted_unit(byte_unit::ByteUnit::ZB).to_string(), - )), - "zib" => Ok(UntaggedValue::string( - byte.get_adjusted_unit(byte_unit::ByteUnit::ZiB).to_string(), - )), - _ => Err(ShellError::labeled_error( - format!("Invalid format code: {:}", format.item()), - "invalid format", - format.tag(), - )), - }; - match value { - Ok(b) => Ok(Value { value: b, ..bytes }), - Err(e) => Err(e), + if let Some(value) = b.to_u128() { + let byte = byte_unit::Byte::from_bytes(value); + let value = match format.item().to_lowercase().as_str() { + "b" => Ok(UntaggedValue::string( + value.to_formatted_string(&Locale::en), + )), + "kb" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::KB).to_string(), + )), + "kib" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::KiB).to_string(), + )), + "mb" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::MB).to_string(), + )), + "mib" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::MiB).to_string(), + )), + "gb" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::GB).to_string(), + )), + "gib" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::GiB).to_string(), + )), + "tb" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::TB).to_string(), + )), + "tib" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::TiB).to_string(), + )), + "pb" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::PB).to_string(), + )), + "pib" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::PiB).to_string(), + )), + "eb" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::EB).to_string(), + )), + "eib" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::EiB).to_string(), + )), + "zb" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::ZB).to_string(), + )), + "zib" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::ZiB).to_string(), + )), + _ => Err(ShellError::labeled_error( + format!("Invalid format code: {:}", format.item()), + "invalid format", + format.tag(), + )), + }; + match value { + Ok(b) => Ok(Value { value: b, ..bytes }), + Err(e) => Err(e), + } + } else { + Err(ShellError::labeled_error( + "Value too large to fit in 128 bits", + "value too large to fit in format", + format.span(), + )) } } _ => Err(ShellError::labeled_error( diff --git a/crates/nu-command/src/commands/math/avg.rs b/crates/nu-command/src/commands/math/avg.rs index fd18632c95..8b39414a04 100644 --- a/crates/nu-command/src/commands/math/avg.rs +++ b/crates/nu-command/src/commands/math/avg.rs @@ -59,12 +59,9 @@ impl WholeStreamCommand for SubCommand { fn to_byte(value: &Value) -> Option { match &value.value { - UntaggedValue::Primitive(Primitive::Int(num)) => Some( - UntaggedValue::Primitive(Primitive::Filesize(convert_number_to_u64(&Number::Int( - num.clone(), - )))) - .into_untagged_value(), - ), + UntaggedValue::Primitive(Primitive::Int(num)) => { + Some(UntaggedValue::Primitive(Primitive::Filesize(num.clone())).into_untagged_value()) + } _ => None, } } @@ -95,7 +92,7 @@ pub fn average(values: &[Value], name: &Tag) -> Result { Value { value: UntaggedValue::Primitive(Primitive::Filesize(num)), .. - } => UntaggedValue::int(*num as usize).into_untagged_value(), + } => UntaggedValue::int(num.clone()).into_untagged_value(), other => other.clone(), }) .collect::>(), @@ -116,7 +113,7 @@ pub fn average(values: &[Value], name: &Tag) -> Result { value: UntaggedValue::Primitive(Primitive::Filesize(num)), .. } => { - let left = UntaggedValue::from(Primitive::Int(num.into())); + let left = UntaggedValue::from(Primitive::Int(num)); let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows); match result { diff --git a/crates/nu-command/src/commands/math/median.rs b/crates/nu-command/src/commands/math/median.rs index c5721e6ed2..0e6ff5c448 100644 --- a/crates/nu-command/src/commands/math/median.rs +++ b/crates/nu-command/src/commands/math/median.rs @@ -137,7 +137,7 @@ fn compute_average(values: &[Value], name: impl Into) -> Result { - let left = UntaggedValue::from(Primitive::Int(num.into())); + let left = UntaggedValue::from(Primitive::Int(num)); let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows); match result { diff --git a/crates/nu-command/src/commands/math/product.rs b/crates/nu-command/src/commands/math/product.rs index 3372c680ef..906dc0d151 100644 --- a/crates/nu-command/src/commands/math/product.rs +++ b/crates/nu-command/src/commands/math/product.rs @@ -3,10 +3,7 @@ use crate::commands::math::utils::run_with_function; use crate::prelude::*; use nu_engine::WholeStreamCommand; use nu_errors::ShellError; -use nu_protocol::{ - hir::{convert_number_to_u64, Number}, - Primitive, Signature, UntaggedValue, Value, -}; +use nu_protocol::{Primitive, Signature, UntaggedValue, Value}; pub struct SubCommand; @@ -51,12 +48,9 @@ impl WholeStreamCommand for SubCommand { fn to_byte(value: &Value) -> Option { match &value.value { - UntaggedValue::Primitive(Primitive::Int(num)) => Some( - UntaggedValue::Primitive(Primitive::Filesize(convert_number_to_u64(&Number::Int( - num.clone(), - )))) - .into_untagged_value(), - ), + UntaggedValue::Primitive(Primitive::Int(num)) => { + Some(UntaggedValue::Primitive(Primitive::Filesize(num.clone())).into_untagged_value()) + } _ => None, } } @@ -78,7 +72,7 @@ pub fn product(values: &[Value], name: &Tag) -> Result { Value { value: UntaggedValue::Primitive(Primitive::Filesize(num)), .. - } => UntaggedValue::int(*num as usize).into_untagged_value(), + } => UntaggedValue::int(num.clone()).into_untagged_value(), other => other.clone(), }) .collect::>(), diff --git a/crates/nu-command/src/commands/math/sum.rs b/crates/nu-command/src/commands/math/sum.rs index ab1a74f203..bfd309d20e 100644 --- a/crates/nu-command/src/commands/math/sum.rs +++ b/crates/nu-command/src/commands/math/sum.rs @@ -4,10 +4,7 @@ use crate::prelude::*; use nu_engine::WholeStreamCommand; use nu_errors::ShellError; -use nu_protocol::{ - hir::{convert_number_to_u64, Number}, - Primitive, Signature, UntaggedValue, Value, -}; +use nu_protocol::{Primitive, Signature, UntaggedValue, Value}; pub struct SubCommand; @@ -59,12 +56,9 @@ impl WholeStreamCommand for SubCommand { fn to_byte(value: &Value) -> Option { match &value.value { - UntaggedValue::Primitive(Primitive::Int(num)) => Some( - UntaggedValue::Primitive(Primitive::Filesize(convert_number_to_u64(&Number::Int( - num.clone(), - )))) - .into_untagged_value(), - ), + UntaggedValue::Primitive(Primitive::Int(num)) => { + Some(UntaggedValue::Primitive(Primitive::Filesize(num.clone())).into_untagged_value()) + } _ => None, } } @@ -90,7 +84,7 @@ pub fn summation(values: &[Value], name: &Tag) -> Result { Value { value: UntaggedValue::Primitive(Primitive::Filesize(num)), .. - } => UntaggedValue::int(*num as usize).into_untagged_value(), + } => UntaggedValue::int(num.clone()).into_untagged_value(), other => other.clone(), }) .collect::>(), diff --git a/crates/nu-command/src/commands/math/variance.rs b/crates/nu-command/src/commands/math/variance.rs index bd79aff630..a6b6a81f91 100644 --- a/crates/nu-command/src/commands/math/variance.rs +++ b/crates/nu-command/src/commands/math/variance.rs @@ -130,7 +130,7 @@ fn sum_of_squares(values: &[Value], name: &Tag) -> Result { value: UntaggedValue::Primitive(Primitive::Filesize(num)), .. } => { - UntaggedValue::from(Primitive::Int(num.clone().into())) + UntaggedValue::from(Primitive::Int(num.clone())) }, Value { value: UntaggedValue::Primitive(num), diff --git a/crates/nu-command/src/commands/to_delimited_data.rs b/crates/nu-command/src/commands/to_delimited_data.rs index f4fb58d2fa..1a7cef31a5 100644 --- a/crates/nu-command/src/commands/to_delimited_data.rs +++ b/crates/nu-command/src/commands/to_delimited_data.rs @@ -116,7 +116,7 @@ pub fn clone_tagged_value(v: &Value) -> Value { UntaggedValue::Primitive(Primitive::FilePath(x.clone())) } UntaggedValue::Primitive(Primitive::Filesize(b)) => { - UntaggedValue::Primitive(Primitive::Filesize(*b)) + UntaggedValue::Primitive(Primitive::Filesize(b.clone())) } UntaggedValue::Primitive(Primitive::Date(d)) => { UntaggedValue::Primitive(Primitive::Date(*d)) diff --git a/crates/nu-command/src/commands/to_toml.rs b/crates/nu-command/src/commands/to_toml.rs index 88af79ac9e..613932c8f1 100644 --- a/crates/nu-command/src/commands/to_toml.rs +++ b/crates/nu-command/src/commands/to_toml.rs @@ -40,7 +40,17 @@ impl WholeStreamCommand for ToTOML { fn helper(v: &Value) -> Result { Ok(match &v.value { UntaggedValue::Primitive(Primitive::Boolean(b)) => toml::Value::Boolean(*b), - UntaggedValue::Primitive(Primitive::Filesize(b)) => toml::Value::Integer(*b as i64), + UntaggedValue::Primitive(Primitive::Filesize(b)) => { + if let Some(value) = b.to_i64() { + toml::Value::Integer(value) + } else { + return Err(ShellError::labeled_error( + "Value too large to write to toml", + "value too large for toml", + v.tag.span, + )); + } + } UntaggedValue::Primitive(Primitive::Duration(i)) => toml::Value::String(i.to_string()), UntaggedValue::Primitive(Primitive::Date(d)) => toml::Value::String(d.to_string()), UntaggedValue::Primitive(Primitive::EndOfStream) => { diff --git a/crates/nu-data/src/base.rs b/crates/nu-data/src/base.rs index 0c48ab2020..28a1a059d3 100644 --- a/crates/nu-data/src/base.rs +++ b/crates/nu-data/src/base.rs @@ -129,20 +129,18 @@ pub fn coerce_compare_primitive( (Int(left), Decimal(right)) => { CompareValues::Decimals(BigDecimal::zero() + left, right.clone()) } - (Int(left), Filesize(right)) => CompareValues::Ints(left.clone(), BigInt::from(*right)), + (Int(left), Filesize(right)) => CompareValues::Ints(left.clone(), right.clone()), (Decimal(left), Decimal(right)) => CompareValues::Decimals(left.clone(), right.clone()), (Decimal(left), Int(right)) => { CompareValues::Decimals(left.clone(), BigDecimal::zero() + right) } (Decimal(left), Filesize(right)) => { - CompareValues::Decimals(left.clone(), BigDecimal::from(*right)) + CompareValues::Decimals(left.clone(), BigDecimal::from(right.clone())) } - (Filesize(left), Filesize(right)) => { - CompareValues::Ints(BigInt::from(*left), BigInt::from(*right)) - } - (Filesize(left), Int(right)) => CompareValues::Ints(BigInt::from(*left), right.clone()), + (Filesize(left), Filesize(right)) => CompareValues::Ints(left.clone(), right.clone()), + (Filesize(left), Int(right)) => CompareValues::Ints(left.clone(), right.clone()), (Filesize(left), Decimal(right)) => { - CompareValues::Decimals(BigDecimal::from(*left), right.clone()) + CompareValues::Decimals(BigDecimal::from(left.clone()), right.clone()) } (Nothing, Nothing) => CompareValues::Booleans(true, true), (String(left), String(right)) => CompareValues::String(left.clone(), right.clone()), diff --git a/crates/nu-data/src/base/shape.rs b/crates/nu-data/src/base/shape.rs index d84cf26e9f..54b9cd3e1f 100644 --- a/crates/nu-data/src/base/shape.rs +++ b/crates/nu-data/src/base/shape.rs @@ -24,7 +24,7 @@ pub enum InlineShape { Int(BigInt), Decimal(BigDecimal), Range(Box), - Bytesize(u64), + Bytesize(BigInt), String(String), Line(String), ColumnPath(ColumnPath), @@ -68,7 +68,7 @@ impl InlineShape { })) } Primitive::Decimal(decimal) => InlineShape::Decimal(decimal.clone()), - Primitive::Filesize(bytesize) => InlineShape::Bytesize(*bytesize), + Primitive::Filesize(bytesize) => InlineShape::Bytesize(bytesize.clone()), Primitive::String(string) => InlineShape::String(string.clone()), Primitive::ColumnPath(path) => InlineShape::ColumnPath(path.clone()), Primitive::GlobPattern(pattern) => InlineShape::GlobPattern(pattern.clone()), @@ -128,7 +128,9 @@ impl InlineShape { } } - pub fn format_bytes(bytesize: &u64) -> (DbgDocBldr, String) { + pub fn format_bytes(bytesize: &BigInt) -> (DbgDocBldr, String) { + use bigdecimal::ToPrimitive; + // get the config value, if it doesn't exist make it 'auto' so it works how it originally did let filesize_format_var = crate::config::config(Tag::unknown()) .expect("unable to get the config.toml file") @@ -155,32 +157,41 @@ impl InlineShape { _ => (byte_unit::ByteUnit::B, "auto"), }; - let byte = byte_unit::Byte::from_bytes(*bytesize as u128); - let byte = if filesize_format.0 == byte_unit::ByteUnit::B && filesize_format.1 == "auto" { - byte.get_appropriate_unit(false) - } else { - byte.get_adjusted_unit(filesize_format.0) - }; + if let Some(value) = bytesize.to_u128() { + let byte = byte_unit::Byte::from_bytes(value); + let byte = if filesize_format.0 == byte_unit::ByteUnit::B && filesize_format.1 == "auto" + { + byte.get_appropriate_unit(false) + } else { + byte.get_adjusted_unit(filesize_format.0) + }; - match byte.get_unit() { - byte_unit::ByteUnit::B => { - let locale_byte = byte.get_value() as u64; - let locale_byte_string = locale_byte.to_formatted_string(&Locale::en); - if filesize_format.1 == "auto" { - let doc = (DbgDocBldr::primitive(locale_byte_string) - + DbgDocBldr::space() - + DbgDocBldr::kind("B")) - .group(); - (doc.clone(), InlineShape::render_doc(&doc)) - } else { - let doc = (DbgDocBldr::primitive(locale_byte_string)).group(); + match byte.get_unit() { + byte_unit::ByteUnit::B => { + let locale_byte = byte.get_value() as u64; + let locale_byte_string = locale_byte.to_formatted_string(&Locale::en); + if filesize_format.1 == "auto" { + let doc = (DbgDocBldr::primitive(locale_byte_string) + + DbgDocBldr::space() + + DbgDocBldr::kind("B")) + .group(); + (doc.clone(), InlineShape::render_doc(&doc)) + } else { + let doc = (DbgDocBldr::primitive(locale_byte_string)).group(); + (doc.clone(), InlineShape::render_doc(&doc)) + } + } + _ => { + let doc = DbgDocBldr::primitive(byte.format(1)); (doc.clone(), InlineShape::render_doc(&doc)) } } - _ => { - let doc = DbgDocBldr::primitive(byte.format(1)); - (doc.clone(), InlineShape::render_doc(&doc)) - } + } else { + let doc = (DbgDocBldr::primitive(format!("{}", bytesize)) + + DbgDocBldr::space() + + DbgDocBldr::kind("B")) + .group(); + (doc.clone(), InlineShape::render_doc(&doc)) } } diff --git a/crates/nu-data/src/config.rs b/crates/nu-data/src/config.rs index 4acc13dda0..6e713febc6 100644 --- a/crates/nu-data/src/config.rs +++ b/crates/nu-data/src/config.rs @@ -61,9 +61,21 @@ fn collect_values(input: &[Value]) -> Result, ShellError> { // Helper method to recursively convert nu_protocol::Value -> toml::Value // This shouldn't be called at the top-level fn helper(v: &Value) -> Result { + use bigdecimal::ToPrimitive; + Ok(match &v.value { UntaggedValue::Primitive(Primitive::Boolean(b)) => toml::Value::Boolean(*b), - UntaggedValue::Primitive(Primitive::Filesize(b)) => toml::Value::Integer(*b as i64), + UntaggedValue::Primitive(Primitive::Filesize(b)) => { + if let Some(value) = b.to_i64() { + toml::Value::Integer(value) + } else { + return Err(ShellError::labeled_error( + "Value too large to convert to toml value", + "value too large", + v.tag.span, + )); + } + } UntaggedValue::Primitive(Primitive::Duration(i)) => toml::Value::String(i.to_string()), UntaggedValue::Primitive(Primitive::Date(d)) => toml::Value::String(d.to_string()), UntaggedValue::Primitive(Primitive::EndOfStream) => { diff --git a/crates/nu-data/src/value.rs b/crates/nu-data/src/value.rs index 7294e2e9bc..6022adbf1b 100644 --- a/crates/nu-data/src/value.rs +++ b/crates/nu-data/src/value.rs @@ -81,21 +81,12 @@ pub fn unsafe_compute_values( match (left, right) { (UntaggedValue::Primitive(lhs), UntaggedValue::Primitive(rhs)) => match (lhs, rhs) { (Primitive::Filesize(x), Primitive::Int(y)) => match operator { - Operator::Plus => Ok(UntaggedValue::Primitive(Primitive::Int(x + y))), - Operator::Minus => Ok(UntaggedValue::Primitive(Primitive::Int(x - y))), - Operator::Multiply => Ok(UntaggedValue::Primitive(Primitive::Int(x * y))), - Operator::Divide => Ok(UntaggedValue::Primitive(Primitive::Decimal( - bigdecimal::BigDecimal::from(*x) / bigdecimal::BigDecimal::from(y.clone()), - ))), + Operator::Multiply => Ok(UntaggedValue::Primitive(Primitive::Filesize(x * y))), + Operator::Divide => Ok(UntaggedValue::Primitive(Primitive::Filesize(x / y))), _ => Err((left.type_name(), right.type_name())), }, (Primitive::Int(x), Primitive::Filesize(y)) => match operator { - Operator::Plus => Ok(UntaggedValue::Primitive(Primitive::Int(x + y))), - Operator::Minus => Ok(UntaggedValue::Primitive(Primitive::Int(x - y))), - Operator::Multiply => Ok(UntaggedValue::Primitive(Primitive::Int(x * y))), - Operator::Divide => Ok(UntaggedValue::Primitive(Primitive::Decimal( - bigdecimal::BigDecimal::from(x.clone()) / bigdecimal::BigDecimal::from(*y), - ))), + Operator::Multiply => Ok(UntaggedValue::Primitive(Primitive::Filesize(x * y))), _ => Err((left.type_name(), right.type_name())), }, _ => Err((left.type_name(), right.type_name())), @@ -120,8 +111,12 @@ pub fn compute_values( Ok(UntaggedValue::Primitive(Primitive::Filesize(result))) } (Primitive::Filesize(x), Primitive::Int(y)) => match operator { - Operator::Multiply => Ok(UntaggedValue::Primitive(Primitive::Int(x * y))), - Operator::Divide => Ok(UntaggedValue::Primitive(Primitive::Int(x / y))), + Operator::Multiply => Ok(UntaggedValue::Primitive(Primitive::Filesize(x * y))), + Operator::Divide => Ok(UntaggedValue::Primitive(Primitive::Filesize(x / y))), + _ => Err((left.type_name(), right.type_name())), + }, + (Primitive::Int(x), Primitive::Filesize(y)) => match operator { + Operator::Multiply => Ok(UntaggedValue::Primitive(Primitive::Filesize(x * y))), _ => Err((left.type_name(), right.type_name())), }, (Primitive::Int(x), Primitive::Int(y)) => match operator { diff --git a/crates/nu-protocol/src/hir.rs b/crates/nu-protocol/src/hir.rs index d27a750d8d..0dcf62bf17 100644 --- a/crates/nu-protocol/src/hir.rs +++ b/crates/nu-protocol/src/hir.rs @@ -631,8 +631,8 @@ impl Unit { } } -pub fn filesize(size_in_bytes: u64) -> UntaggedValue { - UntaggedValue::Primitive(Primitive::Filesize(size_in_bytes)) +pub fn filesize(size_in_bytes: impl Into) -> UntaggedValue { + UntaggedValue::Primitive(Primitive::Filesize(size_in_bytes.into())) } pub fn duration(nanos: BigInt) -> UntaggedValue { diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index 93ef6ec82c..56238a2515 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -186,7 +186,7 @@ impl UntaggedValue { } /// Helper for creating filesize values - pub fn filesize(s: impl Into) -> UntaggedValue { + pub fn filesize(s: impl Into) -> UntaggedValue { UntaggedValue::Primitive(Primitive::Filesize(s.into())) } @@ -713,7 +713,7 @@ impl U64Ext for u64 { fn to_filesize_value(&self, the_tag: Tag) -> Value { Value { - value: UntaggedValue::Primitive(Primitive::Filesize(*self)), + value: UntaggedValue::Primitive(Primitive::Filesize(BigInt::from(*self))), tag: the_tag, } } diff --git a/crates/nu-protocol/src/value/primitive.rs b/crates/nu-protocol/src/value/primitive.rs index 04be5326d5..695bbf5392 100644 --- a/crates/nu-protocol/src/value/primitive.rs +++ b/crates/nu-protocol/src/value/primitive.rs @@ -31,7 +31,7 @@ pub enum Primitive { #[serde(with = "serde_bigdecimal")] Decimal(BigDecimal), /// A count in the number of bytes, used as a filesize - Filesize(u64), + Filesize(BigInt), /// A string value String(String), /// A path to travel to reach a value in a table @@ -254,17 +254,21 @@ pub fn format_primitive(primitive: &Primitive, field_name: Option<&String>) -> S Primitive::EndOfStream => String::new(), Primitive::FilePath(p) => format!("{}", p.display()), Primitive::Filesize(num_bytes) => { - let byte = byte_unit::Byte::from_bytes(*num_bytes as u128); + if let Some(value) = num_bytes.to_u128() { + let byte = byte_unit::Byte::from_bytes(value); - if byte.get_bytes() == 0u128 { - return "—".to_string(); - } + if byte.get_bytes() == 0u128 { + return "—".to_string(); + } - let byte = byte.get_appropriate_unit(false); + let byte = byte.get_appropriate_unit(false); - match byte.get_unit() { - byte_unit::ByteUnit::B => format!("{} B ", byte.get_value()), - _ => byte.format(1), + match byte.get_unit() { + byte_unit::ByteUnit::B => format!("{} B ", byte.get_value()), + _ => byte.format(1), + } + } else { + format!("{} B", num_bytes) } } Primitive::Duration(duration) => format_duration(duration), diff --git a/tests/shell/pipeline/commands/internal.rs b/tests/shell/pipeline/commands/internal.rs index 4cb2e98e16..1270276831 100644 --- a/tests/shell/pipeline/commands/internal.rs +++ b/tests/shell/pipeline/commands/internal.rs @@ -750,6 +750,53 @@ fn range_with_mixed_types() { assert_eq!(actual.out, "55"); } +#[test] +fn filesize_math() { + let actual = nu!( + cwd: ".", + r#" + = 100 * 10kb + "# + ); + + assert_eq!(actual.out, "1.0 MB"); +} + +#[test] +fn filesize_math2() { + let actual = nu!( + cwd: ".", + r#" + = 100 / 10kb + "# + ); + + assert!(actual.err.contains("Coercion")); +} + +#[test] +fn filesize_math3() { + let actual = nu!( + cwd: ".", + r#" + = 100kb / 10 + "# + ); + + assert_eq!(actual.out, "10.2 KB"); +} +#[test] +fn filesize_math4() { + let actual = nu!( + cwd: ".", + r#" + = 100kb * 5 + "# + ); + + assert_eq!(actual.out, "512.0 KB"); +} + #[test] fn exclusive_range_with_mixed_types() { let actual = nu!(