Add Filesize type

This commit is contained in:
Ian Manske 2024-10-20 23:50:16 -07:00
parent ea6493c041
commit d59f464156
2 changed files with 277 additions and 3 deletions

View File

@ -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<Self> {
if let Some(bytes) = value.checked_mul(unit.as_bytes() as i64) {
Some(Self(bytes))
} else {
None
}
}
}
impl From<i64> for Filesize {
fn from(value: i64) -> Self {
Self(value)
}
}
impl From<Filesize> for i64 {
fn from(filesize: Filesize) -> Self {
filesize.0
}
}
impl TryFrom<u64> for Filesize {
type Error = <u64 as TryInto<i64>>::Error;
fn try_from(value: u64) -> Result<Self, Self::Error> {
value.try_into().map(Self)
}
}
impl TryFrom<Filesize> for u64 {
type Error = <i64 as TryInto<u64>>::Error;
fn try_from(filesize: Filesize) -> Result<Self, Self::Error> {
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<Filesize> for $ty {
type Error = <i64 as TryInto<$ty>>::Error;
#[inline]
fn try_from(filesize: Filesize) -> Result<Self, Self::Error> {
filesize.0.try_into()
}
}
)*
};
}
impl_from!(u8, i8, u16, i16, u32, i32);
impl FromValue for Filesize {
fn from_value(value: Value) -> Result<Self, ShellError> {
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<Self>;
fn add(self, rhs: Self) -> Self::Output {
self.0.checked_add(rhs.0).map(Self)
}
}
impl Sub for Filesize {
type Output = Option<Self>;
fn sub(self, rhs: Self) -> Self::Output {
self.0.checked_sub(rhs.0).map(Self)
}
}
impl Mul for Filesize {
type Output = Option<Self>;
fn mul(self, rhs: Self) -> Self::Output {
self.0.checked_mul(rhs.0).map(Self)
}
}
impl Div for Filesize {
type Output = Option<Self>;
fn div(self, rhs: Self) -> Self::Output {
self.0.checked_div(rhs.0).map(Self)
}
}
impl Rem for Filesize {
type Output = Option<Self>;
fn rem(self, rhs: Self) -> Self::Output {
self.0.checked_rem(rhs.0).map(Self)
}
}
impl Neg for Filesize {
type Output = Option<Self>;
fn neg(self) -> Self::Output {
self.0.checked_neg().map(Self)
}
}
impl Sum<Filesize> for Option<Filesize> {
fn sum<I: Iterator<Item = Filesize>>(iter: I) -> Self {
let mut sum = Filesize::ZERO;
for filesize in iter {
sum = (sum + filesize)?;
}
Some(sum)
}
}
impl Product<Filesize> for Option<Filesize> {
fn product<I: Iterator<Item = Filesize>>(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

View File

@ -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<i64, ShellError> {
pub fn as_filesize(&self) -> Result<Filesize, ShellError> {
if let Value::Filesize { val, .. } = self {
Ok(*val)
Ok(Filesize::new(*val))
} else {
self.cant_convert_to("filesize")
}