usenetnews-dynexp2 0.1.2

USENET news server expiry time dynamic tuning (for INN2)
Documentation
// Copyright 2022 Ian Jackson
// SPDX-License-Identifier: GPL-3.0-or-later
// There is NO WARRANTY.

use crate::prelude::*;

/// KiB
#[derive(Debug,Copy,Clone,PartialEq,PartialOrd)]
pub struct Space(pub f64);

#[derive(Debug,Copy,Clone,PartialEq,PartialOrd)]
pub struct Days(pub f64);

#[derive(Debug,Copy,Clone,PartialEq,PartialOrd)]
pub struct Lambda(pub f64);

#[derive(Debug,Copy,Clone,PartialEq,PartialOrd)]
// might be Inf
pub struct DaysOrNever(f64);

macro_rules! impl_eq_ord { { $( $ty:ident )* } => { $(
  impl Eq for $ty { }
  #[allow(clippy::derive_ord_xor_partial_ord)]
  impl Ord for $ty {
    fn cmp(&self, other: &$ty) -> Ordering {
      PartialOrd::partial_cmp(self, other).expect("was NaN!")
    }
  }
)* } }

macro_rules! impl_fromstr { { $( $ty:ident )* } => { $(
  impl FromStr for $ty {
    type Err = AE;
    fn from_str(s: &str) -> AR<Self> {
      let v: f64 = s.parse()?;
      if ! v.is_finite() { return Err(anyhow!("NaN and Inf not allowed")); }
      Ok($ty(v))
    }
  }
)* } }

macro_rules! impl_binary { { $Name:ident, $op:tt, $( $ty:ident )* } => {
  $(
    paste!{
      impl ops::$Name<$ty> for $ty {
        type Output = $ty;
        fn [< $Name:lower >](self, other: $ty) -> Self {
          $ty(self.0 $op other.0)
        }
      }
      impl ops::[< $Name Assign >]<$ty> for $ty {
        fn [< $Name:lower _assign >](&mut self, other: $ty) {
          self.0 = self.0 $op other.0;
        }
      }
    }
  )*
} }

macro_rules! impl_unary { { $Name:ident, $op:tt, $( $ty:ident )* } => {
  $(
    paste!{
      impl ops::$Name for $ty {
        type Output = $ty;
        fn [< $Name:lower >](self) -> Self {
          $ty($op self.0)
        }
      }
    }
  )*
} }

macro_rules! impl_by_scalar { { $Name:ident, $op:tt, $( $ty:ident )* } => {
  $(
    paste!{
      impl ops::$Name<f64> for $ty {
        type Output = $ty;
        fn [< $Name:lower >](self, other: f64) -> Self {
          $ty(self.0 $op other)
        }
      }
      impl ops::[< $Name Assign >]<f64> for $ty {
        fn [< $Name:lower _assign >](&mut self, other: f64) {
          self.0 = self.0 $op other;
        }
      }
    }
  )*
} }

impl_eq_ord!    (        Days Lambda Space DaysOrNever);
impl_fromstr!   (                    Space);

impl_binary!    (Add, +, Days Lambda);
impl_binary!    (Sub, -, Days Lambda);
impl_unary!     (Neg, -, Days Lambda);

impl_by_scalar! (Mul, *, Days);

impl FromStr for DaysOrNever {
  type Err = AE;
  fn from_str(s: &str) -> AR<Self> {
    let v = if s == "never" {
      DaysOrNever::never()
    } else {
      let v: f64 = s.parse()?;
      if ! v.is_finite() { return Err(anyhow!("NaN and Inf not allowed")); }
      DaysOrNever(v)
    };
    Ok(v)
  }
}

impl FromStr for Days {
  type Err = AE;
  fn from_str(s: &str) -> AR<Self> {
    let don: DaysOrNever = s.parse()?;
    don.try_into()
      .map_err(|NeverError| anyhow!(r#""never" not allowed here"#))
  }
}

impl Display for Days {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    let v = self.0;
    if let Some(width) = f.width().or(
      if f.alternate() { Some(7) } else { None }
    ) {
      write!(f, "{:width$.2}", v, width=width)
    } else if v.fract() == 0.0 {
      write!(f, "{}", v)
    } else {
      write!(f, "{:.2}", v)
    }
  }
}

impl Display for DaysOrNever {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    match Days::try_from(*self) {
      Err(NeverError) => Display::fmt("never", f),
      Ok(days) => Display::fmt(&days, f),
    }
  }
}

impl Display for Lambda {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    let v = self.0;
    write!(f, "{:6.2}%", v * 100.)
  }
}

impl Display for Space {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    let v = self.0;
    let s = format!("{:.0}", v);
    let s: String = 
      iter::once(None).chain(
        IntoIterator::into_iter([None, None, Some('_')]).cycle()
      )
      .zip(
        s.chars().rev()
      )
      .flat_map(|(d, c)| d.into_iter().chain(iter::once(c)))
      .collect();
    let s: String = s.chars().rev().collect();
    f.pad_integral(true, "", &s)
  }
}

#[derive(Debug, Clone)]
pub struct NeverError;

impl TryFrom<DaysOrNever> for Days {
  type Error = NeverError;
  fn try_from(don: DaysOrNever) -> Result<Days, NeverError> {
    let v = don.0;
    if v.is_finite() {
      Ok(Days(v))
    } else {
      Err(NeverError)
    }
  }
}

impl From<Days> for DaysOrNever {
  fn from(days: Days) -> DaysOrNever {
    DaysOrNever(days.0)
  }
}

impl DaysOrNever {
  pub fn never() -> Self { DaysOrNever(f64::INFINITY) }
}

pub struct DaysRange(pub [Days; 2]);

impl DaysRange {
  pub fn clamp(&self, days: Days) -> Days {
    let [min, max] = self.0;
    days.clamp(min, max)
  }

  pub fn mk_lambda(self, val: Days) -> Lambda {
    let [min, max] = self.0;
    let lambda = (val - min).0 / (max - min).0;
    Lambda(lambda)
  }

  pub fn mk_days(self, lambda: Lambda) -> Days {
    let [Days(min), Days(max)] = self.0;
    let val = min + (max - min) * lambda.0;
    Days(val)
  }
}

impl From<[Days; 3]> for Lambda {
  fn from([min, val, max]: [Days; 3]) -> Lambda {
    Lambda(
      (val - min).0 / (max - min).0
    )
  }
}