From d59f464156f82443604e6b2e9fb22f3e524d08dd Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Sun, 20 Oct 2024 23:50:16 -0700 Subject: [PATCH] Add `Filesize` type --- crates/nu-protocol/src/value/filesize.rs | 276 ++++++++++++++++++++++- crates/nu-protocol/src/value/mod.rs | 4 +- 2 files changed, 277 insertions(+), 3 deletions(-) diff --git a/crates/nu-protocol/src/value/filesize.rs b/crates/nu-protocol/src/value/filesize.rs index 9d58c103df..4e2cd7ce07 100644 --- a/crates/nu-protocol/src/value/filesize.rs +++ b/crates/nu-protocol/src/value/filesize.rs @@ -1,7 +1,281 @@ -use crate::Config; +use crate::{Config, FromValue, IntoValue, ShellError, Span, Type, Value}; use byte_unit::UnitType; use nu_utils::get_system_locale; use num_format::ToFormattedString; +use serde::{Deserialize, Serialize}; +use std::{ + fmt, + iter::{Product, Sum}, + ops::{Add, Div, Mul, Neg, Rem, Sub}, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[repr(transparent)] +#[serde(transparent)] +pub struct Filesize(i64); + +impl Filesize { + pub const ZERO: Self = Self(0); + + pub const fn new(bytes: i64) -> Self { + Self(bytes) + } + + pub const fn get(&self) -> i64 { + self.0 + } + + pub const fn from_unit(value: i64, unit: FilesizeUnit) -> Option { + if let Some(bytes) = value.checked_mul(unit.as_bytes() as i64) { + Some(Self(bytes)) + } else { + None + } + } +} + +impl From for Filesize { + fn from(value: i64) -> Self { + Self(value) + } +} + +impl From for i64 { + fn from(filesize: Filesize) -> Self { + filesize.0 + } +} + +impl TryFrom for Filesize { + type Error = >::Error; + + fn try_from(value: u64) -> Result { + value.try_into().map(Self) + } +} + +impl TryFrom for u64 { + type Error = >::Error; + + fn try_from(filesize: Filesize) -> Result { + filesize.0.try_into() + } +} + +macro_rules! impl_from { + ($($ty:ty),* $(,)?) => { + $( + impl From<$ty> for Filesize { + #[inline] + fn from(value: $ty) -> Self { + Self(value.into()) + } + } + + impl TryFrom for $ty { + type Error = >::Error; + + #[inline] + fn try_from(filesize: Filesize) -> Result { + filesize.0.try_into() + } + } + )* + }; +} + +impl_from!(u8, i8, u16, i16, u32, i32); + +impl FromValue for Filesize { + fn from_value(value: Value) -> Result { + value.as_filesize() + } + + fn expected_type() -> Type { + Type::Filesize + } +} + +impl IntoValue for Filesize { + fn into_value(self, span: Span) -> Value { + Value::filesize(self.0, span) + } +} + +impl Add for Filesize { + type Output = Option; + + fn add(self, rhs: Self) -> Self::Output { + self.0.checked_add(rhs.0).map(Self) + } +} + +impl Sub for Filesize { + type Output = Option; + + fn sub(self, rhs: Self) -> Self::Output { + self.0.checked_sub(rhs.0).map(Self) + } +} + +impl Mul for Filesize { + type Output = Option; + + fn mul(self, rhs: Self) -> Self::Output { + self.0.checked_mul(rhs.0).map(Self) + } +} + +impl Div for Filesize { + type Output = Option; + + fn div(self, rhs: Self) -> Self::Output { + self.0.checked_div(rhs.0).map(Self) + } +} + +impl Rem for Filesize { + type Output = Option; + + fn rem(self, rhs: Self) -> Self::Output { + self.0.checked_rem(rhs.0).map(Self) + } +} + +impl Neg for Filesize { + type Output = Option; + + fn neg(self) -> Self::Output { + self.0.checked_neg().map(Self) + } +} + +impl Sum for Option { + fn sum>(iter: I) -> Self { + let mut sum = Filesize::ZERO; + for filesize in iter { + sum = (sum + filesize)?; + } + Some(sum) + } +} + +impl Product for Option { + fn product>(iter: I) -> Self { + let mut product = Filesize::ZERO; + for filesize in iter { + product = (product * filesize)?; + } + Some(product) + } +} + +impl fmt::Display for Filesize { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + format_filesize(self.0, "auto", Some(false)).fmt(f) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum FilesizeUnit { + B, + KB, + MB, + GB, + TB, + PB, + EB, + KiB, + MiB, + GiB, + TiB, + PiB, + EiB, +} + +impl FilesizeUnit { + pub const fn as_bytes(&self) -> u64 { + match self { + Self::B => 1, + Self::KB => 10_u64.pow(3), + Self::MB => 10_u64.pow(6), + Self::GB => 10_u64.pow(9), + Self::TB => 10_u64.pow(12), + Self::PB => 10_u64.pow(15), + Self::EB => 10_u64.pow(18), + Self::KiB => 1 << 10, + Self::MiB => 1 << 20, + Self::GiB => 1 << 30, + Self::TiB => 1 << 40, + Self::PiB => 1 << 50, + Self::EiB => 1 << 60, + } + } + + pub const fn as_filesize(&self) -> Filesize { + Filesize::new(self.as_bytes() as i64) + } + + pub const fn as_str(&self) -> &'static str { + match self { + Self::B => "B", + Self::KB => "KB", + Self::MB => "MB", + Self::GB => "GB", + Self::TB => "TB", + Self::PB => "PB", + Self::EB => "EB", + Self::KiB => "KiB", + Self::MiB => "MiB", + Self::GiB => "GiB", + Self::TiB => "TiB", + Self::PiB => "PiB", + Self::EiB => "EiB", + } + } + + pub const fn is_decimal(&self) -> bool { + match self { + FilesizeUnit::B + | FilesizeUnit::KB + | FilesizeUnit::MB + | FilesizeUnit::GB + | FilesizeUnit::TB + | FilesizeUnit::PB + | FilesizeUnit::EB => true, + FilesizeUnit::KiB + | FilesizeUnit::MiB + | FilesizeUnit::GiB + | FilesizeUnit::TiB + | FilesizeUnit::PiB + | FilesizeUnit::EiB => false, + } + } + + pub const fn is_binary(&self) -> bool { + match self { + FilesizeUnit::KB + | FilesizeUnit::MB + | FilesizeUnit::GB + | FilesizeUnit::TB + | FilesizeUnit::PB + | FilesizeUnit::EB => false, + FilesizeUnit::B + | FilesizeUnit::KiB + | FilesizeUnit::MiB + | FilesizeUnit::GiB + | FilesizeUnit::TiB + | FilesizeUnit::PiB + | FilesizeUnit::EiB => true, + } + } +} + +impl fmt::Display for FilesizeUnit { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.as_str().fmt(f) + } +} pub fn format_filesize_from_conf(num_bytes: i64, config: &Config) -> String { // We need to take into account config.filesize_metric so, if someone asks for KB diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 1a1e8fada2..277c8d988f 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -301,9 +301,9 @@ impl Value { } /// Returns the inner `i64` filesize value or an error if this `Value` is not a filesize - pub fn as_filesize(&self) -> Result { + pub fn as_filesize(&self) -> Result { if let Value::Filesize { val, .. } = self { - Ok(*val) + Ok(Filesize::new(*val)) } else { self.cant_convert_to("filesize") }