From 7708dac4ab9964e8d1212774cfdd8d0d2aa81caa Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Sat, 16 Nov 2024 18:41:09 -0800 Subject: [PATCH] Add doc comments and other trait implementations --- crates/nu-protocol/src/value/filesize.rs | 155 +++++++++++++++++++---- 1 file changed, 133 insertions(+), 22 deletions(-) diff --git a/crates/nu-protocol/src/value/filesize.rs b/crates/nu-protocol/src/value/filesize.rs index 02dde69276..5baa9e6220 100644 --- a/crates/nu-protocol/src/value/filesize.rs +++ b/crates/nu-protocol/src/value/filesize.rs @@ -7,37 +7,41 @@ use std::{ fmt, iter::Sum, ops::{Add, Mul, Neg, Sub}, + str::FromStr, }; use thiserror::Error; +/// A signed number of bytes. +/// +/// [`Filesize`] is a wrapper around [`i64`]. Whereas [`i64`] is a dimensionless value, [`Filesize`] represents a +/// numerical value with a dimensional unit (byte). +/// +/// A [`Filesize`] can be created from an [`i64`] using [`Filesize::new`] or the `From` or `Into` trait implementations. +/// To get the underlying [`i64`] value, use [`Filesize::get`] or the `From` or `Into` trait implementations. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[repr(transparent)] #[serde(transparent)] pub struct Filesize(i64); impl Filesize { + /// A [`Filesize`] of 0 bytes. pub const ZERO: Self = Self(0); + /// The smallest possible [`Filesize`] value. + pub const MIN: Self = Self(i64::MIN); + + /// The largest possible [`Filesize`] value. + pub const MAX: Self = Self(i64::MAX); + + /// Create a new [`Filesize`] from a [`i64`] number of bytes. pub const fn new(bytes: i64) -> Self { Self(bytes) } - pub const fn get(&self) -> i64 { - self.0 - } - - pub const fn is_positive(self) -> bool { - self.0.is_positive() - } - - pub const fn is_negative(self) -> bool { - self.0.is_negative() - } - - pub const fn signum(self) -> Self { - Self(self.0.signum()) - } - + /// Creates a [`Filesize`] from a signed multiple of a [`FilesizeUnit`]. + /// + /// If the resulting number of bytes calculated by `value * unit.as_bytes()` overflows an + /// [`i64`], then `None` is returned. 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)) @@ -45,6 +49,29 @@ impl Filesize { None } } + + /// Returns the underlying [`i64`] number of bytes in a [`Filesize`]. + pub const fn get(&self) -> i64 { + self.0 + } + + /// Returns true if a [`Filesize`] is positive and false if it is zero or negative. + pub const fn is_positive(self) -> bool { + self.0.is_positive() + } + + /// Returns true if a [`Filesize`] is negative and false if it is zero or positive. + pub const fn is_negative(self) -> bool { + self.0.is_negative() + } + + /// Returns a [`Filesize`] representing the sign of `self`. + /// - 0 if the filesize is zero + /// - 1 if the filesize is positive + /// - -1 if the filesize is negative + pub const fn signum(self) -> Self { + Self(self.0.signum()) + } } impl From for Filesize { @@ -75,6 +102,7 @@ impl TryFrom for u64 { } } +/// The error type returned when a checked conversion from a floating point type fails. #[derive(Debug, Copy, Clone, PartialEq, Eq, Error)] pub struct TryFromFloatError(()); @@ -230,24 +258,45 @@ impl fmt::Display for Filesize { } } +/// All the possible filesize units for a [`Filesize`]. +/// +/// This type contains both units with metric (SI) decimal prefixes which are powers of 10 (e.g., KB = 1000 bytes) +/// and units with binary prefixes which are powers of 2 (e.g., KiB = 1024 bytes). +/// +/// The number of bytes in a [`FilesizeUnit`] can be obtained using +/// [`as_bytes`](FilesizeUnit::as_bytes). #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum FilesizeUnit { + /// One byte B, + /// Kilobyte = 1000 bytes KB, + /// Megabyte = 106 bytes MB, + /// Gigabyte = 109 bytes GB, + /// Terabyte = 1012 bytes TB, + /// Petabyte = 1015 bytes PB, + /// Exabyte = 1018 bytes EB, + /// Kibibyte = 1024 bytes KiB, + /// Mebibyte = 220 bytes MiB, + /// Gibibyte = 230 bytes GiB, + /// Tebibyte = 240 bytes TiB, + /// Pebibyte = 250 bytes PiB, + /// Exbibyte = 260 bytes EiB, } impl FilesizeUnit { + /// Returns the number of bytes in a [`FilesizeUnit`]. pub const fn as_bytes(&self) -> u64 { match self { Self::B => 1, @@ -257,19 +306,33 @@ impl FilesizeUnit { 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, + Self::KiB => 2_u64.pow(10), + Self::MiB => 2_u64.pow(20), + Self::GiB => 2_u64.pow(30), + Self::TiB => 2_u64.pow(40), + Self::PiB => 2_u64.pow(50), + Self::EiB => 2_u64.pow(60), } } + /// Convert a [`FilesizeUnit`] to a [`Filesize`]. + /// + /// To create a [`Filesize`] from a multiple of a [`FilesizeUnit`] use [`Filesize::from_unit`]. pub const fn as_filesize(&self) -> Filesize { Filesize::new(self.as_bytes() as i64) } + /// Returns the abbreviated unit for a [`FilesizeUnit`] as a [`str`]. + /// + /// The abbreviated unit is exactly the same as the enum case name in Rust code. + /// + /// # Examples + /// ``` + /// # use nu_protocol::FilesizeUnit; + /// assert_eq!(FilesizeUnit::B.as_str(), "B"); + /// assert_eq!(FilesizeUnit::KB.as_str(), "KB"); + /// assert_eq!(FilesizeUnit::KiB.as_str(), "KiB"); + /// ``` pub const fn as_str(&self) -> &'static str { match self { Self::B => "B", @@ -288,6 +351,9 @@ impl FilesizeUnit { } } + /// Returns `true` if a [`FilesizeUnit`] has a metric (SI) decimal prefix (a power of 10). + /// + /// Note that this returns `true` for [`FilesizeUnit::B`] as well. pub const fn is_decimal(&self) -> bool { match self { Self::B | Self::KB | Self::MB | Self::GB | Self::TB | Self::PB | Self::EB => true, @@ -295,6 +361,9 @@ impl FilesizeUnit { } } + /// Returns `true` if a [`FilesizeUnit`] has a binary prefix (a power of 2). + /// + /// Note that this returns `true` for [`FilesizeUnit::B`] as well. pub const fn is_binary(&self) -> bool { match self { Self::KB | Self::MB | Self::GB | Self::TB | Self::PB | Self::EB => false, @@ -303,6 +372,48 @@ impl FilesizeUnit { } } +impl From for Filesize { + fn from(unit: FilesizeUnit) -> Self { + unit.as_filesize() + } +} + +/// An error returned when failing to parse a [`FilesizeUnit`]. +/// +/// This occurs when the string being parsed does not exactly match the name of one of the +/// enum cases in [`FilesizeUnit`]. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Error)] +pub struct ParseFilesizeUnitError(()); + +impl fmt::Display for ParseFilesizeUnitError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(fmt, "invalid filesize unit") + } +} + +impl FromStr for FilesizeUnit { + type Err = ParseFilesizeUnitError; + + fn from_str(s: &str) -> Result { + Ok(match s { + "B" => Self::B, + "KB" => Self::KB, + "MB" => Self::MB, + "GB" => Self::GB, + "TB" => Self::TB, + "PB" => Self::PB, + "EB" => Self::EB, + "KiB" => Self::KiB, + "MiB" => Self::MiB, + "GiB" => Self::GiB, + "TiB" => Self::TiB, + "PiB" => Self::PiB, + "EiB" => Self::EiB, + _ => return Err(ParseFilesizeUnitError(())), + }) + } +} + impl fmt::Display for FilesizeUnit { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.as_str().fmt(f)