use std::error::Error;
use std::fmt;
use std::str::FromStr;
pub const KB: i64 = 1 << 10;
pub const MB: i64 = 1 << 20;
pub const GB: i64 = 1 << 30;
pub const TB: i64 = 1 << 40;
pub const PB: i64 = 1 << 50;
pub const EB: i64 = 1 << 60;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SizeOp {
Gt,
Ge,
Lt,
Le,
Eq,
}
impl SizeOp {
pub const ALL: [SizeOp; 5] = [SizeOp::Gt, SizeOp::Ge, SizeOp::Lt, SizeOp::Le, SizeOp::Eq];
#[inline]
#[must_use]
pub fn applies(self, value: i64, threshold: i64) -> bool {
match self {
SizeOp::Gt => value > threshold,
SizeOp::Ge => value >= threshold,
SizeOp::Lt => value < threshold,
SizeOp::Le => value <= threshold,
SizeOp::Eq => value == threshold,
}
}
}
impl fmt::Display for SizeOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
SizeOp::Gt => ">",
SizeOp::Ge => ">=",
SizeOp::Lt => "<",
SizeOp::Le => "<=",
SizeOp::Eq => "=",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SizeFilter {
op: SizeOp,
bytes: i64,
}
impl SizeFilter {
#[inline]
#[must_use]
pub const fn new(op: SizeOp, bytes: i64) -> Self {
SizeFilter { op, bytes }
}
#[inline]
#[must_use]
pub const fn op(self) -> SizeOp {
self.op
}
#[inline]
#[must_use]
pub const fn bytes(self) -> i64 {
self.bytes
}
#[inline]
#[must_use]
pub const fn gt(bytes: i64) -> Self {
SizeFilter {
op: SizeOp::Gt,
bytes,
}
}
#[inline]
#[must_use]
pub const fn ge(bytes: i64) -> Self {
SizeFilter {
op: SizeOp::Ge,
bytes,
}
}
#[inline]
#[must_use]
pub const fn lt(bytes: i64) -> Self {
SizeFilter {
op: SizeOp::Lt,
bytes,
}
}
#[inline]
#[must_use]
pub const fn le(bytes: i64) -> Self {
SizeFilter {
op: SizeOp::Le,
bytes,
}
}
#[inline]
#[must_use]
pub const fn eq(bytes: i64) -> Self {
SizeFilter {
op: SizeOp::Eq,
bytes,
}
}
#[inline]
#[must_use]
pub fn matches(self, value: i64) -> bool {
self.op.applies(value, self.bytes)
}
}
impl fmt::Display for SizeFilter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.op, format_size(self.bytes))
}
}
impl FromStr for SizeFilter {
type Err = SizeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
parse_size_filter(s)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for SizeFilter {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for SizeFilter {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Size(i64);
impl Size {
pub const ZERO: Size = Size(0);
#[inline]
#[must_use]
pub const fn from_bytes(bytes: i64) -> Self {
Size(bytes)
}
#[inline]
#[must_use]
pub const fn from_kb(kb: i64) -> Self {
Size(kb * KB)
}
#[inline]
#[must_use]
pub const fn from_mb(mb: i64) -> Self {
Size(mb * MB)
}
#[inline]
#[must_use]
pub const fn from_gb(gb: i64) -> Self {
Size(gb * GB)
}
#[inline]
#[must_use]
pub const fn from_tb(tb: i64) -> Self {
Size(tb * TB)
}
#[inline]
#[must_use]
pub const fn bytes(self) -> i64 {
self.0
}
}
impl From<i64> for Size {
#[inline]
fn from(v: i64) -> Self {
Size(v)
}
}
impl From<Size> for i64 {
#[inline]
fn from(s: Size) -> Self {
s.0
}
}
impl fmt::Display for Size {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&format_size(self.0))
}
}
impl FromStr for Size {
type Err = SizeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
parse_size(s).map(Size)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Size {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Size {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
impl std::ops::Add for Size {
type Output = Size;
#[inline]
fn add(self, rhs: Size) -> Size {
Size(self.0 + rhs.0)
}
}
impl std::ops::Add<i64> for Size {
type Output = Size;
#[inline]
fn add(self, rhs: i64) -> Size {
Size(self.0 + rhs)
}
}
impl std::ops::Add<Size> for i64 {
type Output = Size;
#[inline]
fn add(self, rhs: Size) -> Size {
Size(self + rhs.0)
}
}
impl std::ops::AddAssign for Size {
#[inline]
fn add_assign(&mut self, rhs: Size) {
self.0 += rhs.0;
}
}
impl std::ops::AddAssign<i64> for Size {
#[inline]
fn add_assign(&mut self, rhs: i64) {
self.0 += rhs;
}
}
impl std::ops::Sub for Size {
type Output = Size;
#[inline]
fn sub(self, rhs: Size) -> Size {
Size(self.0 - rhs.0)
}
}
impl std::ops::Sub<i64> for Size {
type Output = Size;
#[inline]
fn sub(self, rhs: i64) -> Size {
Size(self.0 - rhs)
}
}
impl std::ops::Sub<Size> for i64 {
type Output = Size;
#[inline]
fn sub(self, rhs: Size) -> Size {
Size(self - rhs.0)
}
}
impl std::ops::SubAssign for Size {
#[inline]
fn sub_assign(&mut self, rhs: Size) {
self.0 -= rhs.0;
}
}
impl std::ops::SubAssign<i64> for Size {
#[inline]
fn sub_assign(&mut self, rhs: i64) {
self.0 -= rhs;
}
}
impl std::ops::Mul<i64> for Size {
type Output = Size;
#[inline]
fn mul(self, rhs: i64) -> Size {
Size(self.0 * rhs)
}
}
impl std::ops::Mul<Size> for i64 {
type Output = Size;
#[inline]
fn mul(self, rhs: Size) -> Size {
Size(self * rhs.0)
}
}
impl std::ops::MulAssign<i64> for Size {
#[inline]
fn mul_assign(&mut self, rhs: i64) {
self.0 *= rhs;
}
}
impl std::ops::Div<i64> for Size {
type Output = Size;
#[inline]
fn div(self, rhs: i64) -> Size {
Size(self.0 / rhs)
}
}
impl std::ops::DivAssign<i64> for Size {
#[inline]
fn div_assign(&mut self, rhs: i64) {
self.0 /= rhs;
}
}
impl std::ops::Rem<i64> for Size {
type Output = Size;
#[inline]
fn rem(self, rhs: i64) -> Size {
Size(self.0 % rhs)
}
}
impl std::ops::RemAssign<i64> for Size {
#[inline]
fn rem_assign(&mut self, rhs: i64) {
self.0 %= rhs;
}
}
impl std::ops::Neg for Size {
type Output = Size;
#[inline]
fn neg(self) -> Size {
Size(-self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SizeError {
MissingOperator,
InvalidNumber,
UnknownUnit,
EmptyInput,
}
impl fmt::Display for SizeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
SizeError::MissingOperator => {
"size filter must start with an operator (>=, >, <=, <, =)"
}
SizeError::InvalidNumber => "failed to parse number",
SizeError::UnknownUnit => "unknown size unit",
SizeError::EmptyInput => "empty input",
})
}
}
impl Error for SizeError {}
pub type SizeResult<T> = Result<T, SizeError>;
pub fn parse_size_filter(s: &str) -> SizeResult<SizeFilter> {
let s = s.trim();
let prefixes: &[(&str, SizeOp)] = &[
(">=", SizeOp::Ge),
("<=", SizeOp::Le),
(">", SizeOp::Gt),
("<", SizeOp::Lt),
("=", SizeOp::Eq),
];
let (op, rest) = prefixes
.iter()
.find_map(|&(prefix, op)| s.strip_prefix(prefix).map(|r| (op, r)))
.ok_or(SizeError::MissingOperator)?;
let bytes = parse_size(rest)?;
Ok(SizeFilter { op, bytes })
}
pub fn parse_size(size_str: &str) -> SizeResult<i64> {
let size_str = size_str.trim();
if size_str.is_empty() {
return Err(SizeError::EmptyInput);
}
let search_start = usize::from(size_str.starts_with('-'));
let alpha_pos = size_str[search_start..].find(|c: char| c.is_ascii_alphabetic());
let (num_part, unit) = match alpha_pos {
Some(pos) => size_str.split_at(search_start + pos),
None => (size_str, ""),
};
let num_part = num_part.trim();
if num_part.is_empty() {
return Err(SizeError::InvalidNumber);
}
let multiplier = unit_multiplier(unit.trim()).ok_or(SizeError::UnknownUnit)?;
let (is_negative, num_str) = if let Some(rest) = num_part.strip_prefix('-') {
(true, rest)
} else {
(false, num_part)
};
let (int_part, frac_part, frac_digits) = if let Some(dot_pos) = num_str.find('.') {
let int_str = &num_str[..dot_pos];
let frac_str = &num_str[dot_pos + 1..];
let int_val: i64 = if int_str.is_empty() {
0
} else {
int_str.parse().map_err(|_| SizeError::InvalidNumber)?
};
let frac_val: i64 = frac_str.parse().map_err(|_| SizeError::InvalidNumber)?;
let frac_digits = u32::try_from(frac_str.len()).map_err(|_| SizeError::InvalidNumber)?;
(int_val, frac_val, frac_digits)
} else {
let int_val: i64 = num_str.parse().map_err(|_| SizeError::InvalidNumber)?;
(int_val, 0, 0)
};
let scale = 10i64.pow(frac_digits);
let numerator = i128::from(int_part) * i128::from(scale) + i128::from(frac_part);
let result = numerator * i128::from(multiplier) / i128::from(scale);
let result = i64::try_from(result).map_err(|_| SizeError::InvalidNumber)?;
Ok(if is_negative { -result } else { result })
}
fn unit_multiplier(unit: &str) -> Option<i64> {
if unit.is_empty() || unit.eq_ignore_ascii_case("b") {
Some(1)
} else if unit.eq_ignore_ascii_case("k") || unit.eq_ignore_ascii_case("kb") {
Some(KB)
} else if unit.eq_ignore_ascii_case("m") || unit.eq_ignore_ascii_case("mb") {
Some(MB)
} else if unit.eq_ignore_ascii_case("g") || unit.eq_ignore_ascii_case("gb") {
Some(GB)
} else if unit.eq_ignore_ascii_case("t") || unit.eq_ignore_ascii_case("tb") {
Some(TB)
} else if unit.eq_ignore_ascii_case("p") || unit.eq_ignore_ascii_case("pb") {
Some(PB)
} else if unit.eq_ignore_ascii_case("e") || unit.eq_ignore_ascii_case("eb") {
Some(EB)
} else {
None
}
}
#[must_use]
pub fn format_size(size: i64) -> String {
const UNITS: &[(u64, &str)] = &[
(1u64 << 60, "EB"),
(1u64 << 50, "PB"),
(1u64 << 40, "TB"),
(1u64 << 30, "GB"),
(1u64 << 20, "MB"),
(1u64 << 10, "KB"),
];
let abs = size.unsigned_abs();
let prefix = if size < 0 { "-" } else { "" };
UNITS
.iter()
.find(|&&(threshold, _)| abs >= threshold)
.map_or_else(
|| format!("{prefix}{abs}B"),
#[allow(clippy::cast_precision_loss)]
|&(threshold, unit)| format!("{prefix}{:.1}{unit}", abs as f64 / threshold as f64),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_pb() {
assert_eq!(parse_size("1PB").unwrap(), PB);
assert_eq!(parse_size("1P").unwrap(), PB);
assert_eq!(parse_size("1pb").unwrap(), PB);
assert_eq!(parse_size("2PB").unwrap(), 2 * PB);
}
#[test]
fn test_parse_eb() {
assert_eq!(parse_size("1EB").unwrap(), EB);
assert_eq!(parse_size("1E").unwrap(), EB);
assert_eq!(parse_size("1eb").unwrap(), EB);
assert_eq!(parse_size("2EB").unwrap(), 2 * EB);
}
#[test]
fn test_decimal_precision() {
assert_eq!(parse_size("1.5GB").unwrap(), 1_610_612_736);
assert_eq!(parse_size("0.5KB").unwrap(), 512);
assert_eq!(parse_size("2.25MB").unwrap(), 2_359_296); assert_eq!(parse_size("0.125GB").unwrap(), 134_217_728); }
#[test]
fn test_negative_decimal() {
assert_eq!(parse_size("-1.5GB").unwrap(), -1_610_612_736);
assert_eq!(parse_size("-0.5KB").unwrap(), -512);
}
#[test]
fn test_large_decimal() {
assert_eq!(parse_size("1.1PB").unwrap(), (1.1 * PB as f64) as i64);
}
#[test]
fn test_float_precision_loss() {
let f64_result = (0.3 * GB as f64) as i64;
let int_result = parse_size("0.3GB").unwrap();
assert_eq!(f64_result, int_result);
assert_ne!(0.1_f64 + 0.2_f64, 0.3_f64);
let one_tenth = parse_size("0.1GB").unwrap();
assert_eq!(one_tenth, 107_374_182);
assert_eq!(parse_size("0.1GB").unwrap(), parse_size("0.1GB").unwrap());
assert_eq!(parse_size("0.3GB").unwrap(), parse_size("0.3GB").unwrap());
assert_eq!(parse_size("0.001GB").unwrap(), 1_073_741); assert_eq!(parse_size("0.0001GB").unwrap(), 107_374); assert_eq!(parse_size("0.5GB").unwrap(), GB / 2); }
}