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)]
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 {
pub op: SizeOp,
pub 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 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 (op, rest) = if let Some(r) = s.strip_prefix(">=") {
(SizeOp::Ge, r)
} else if let Some(r) = s.strip_prefix("<=") {
(SizeOp::Le, r)
} else if let Some(r) = s.strip_prefix('>') {
(SizeOp::Gt, r)
} else if let Some(r) = s.strip_prefix('<') {
(SizeOp::Lt, r)
} else if let Some(r) = s.strip_prefix('=') {
(SizeOp::Eq, r)
} else {
return Err(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: f64 = num_part
.trim()
.parse()
.map_err(|_| SizeError::InvalidNumber)?;
let multiplier = unit_multiplier(unit.trim()).ok_or(SizeError::UnknownUnit)?;
Ok((num * multiplier as f64) as i64)
}
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 {
None
}
}
#[must_use]
pub fn format_size(size: i64) -> String {
let abs = size.unsigned_abs();
let prefix = if size < 0 { "-" } else { "" };
if abs >= 1 << 60 {
format!("{}{:.1}EB", prefix, (abs as f64) / ((1u64 << 60) as f64))
} else if abs >= 1 << 50 {
format!("{}{:.1}PB", prefix, (abs as f64) / ((1u64 << 50) as f64))
} else if abs >= 1 << 40 {
format!("{}{:.1}TB", prefix, (abs as f64) / ((1u64 << 40) as f64))
} else if abs >= 1 << 30 {
format!("{}{:.1}GB", prefix, (abs as f64) / ((1u64 << 30) as f64))
} else if abs >= 1 << 20 {
format!("{}{:.1}MB", prefix, (abs as f64) / ((1u64 << 20) as f64))
} else if abs >= 1 << 10 {
format!("{}{:.1}KB", prefix, (abs as f64) / ((1u64 << 10) as f64))
} else {
format!("{}{}B", prefix, abs)
}
}