h5inspect 1.4.0

A terminal based HDF5 file inspector
use duplicate::duplicate_item;
use num_traits::ToPrimitive;
use numfmt::{Formatter, Precision, Scales};
use std::fmt::Display;

pub trait Summable: Sized {
    type AccumulatorType: std::ops::Add<Output = Self::AccumulatorType>
        + num_traits::Zero
        + Copy
        + Display
        + From<Self>
        + ToPrimitive;
}

#[duplicate_item(
    num_type acc_type;
    [ bool ]  [u64];
    [ u8 ]  [u64];
    [ u16 ] [u64];
    [ u32 ] [u64];
    [ u64 ] [u128];
    [ i8 ]  [i64];
    [ i16 ] [i64];
    [ i32 ] [i64];
    [ i64 ] [i128];
    [ f32 ] [f64];
    [ f64 ] [f64];
)]
impl Summable for num_type {
    type AccumulatorType = acc_type;
}

pub trait IsNan {
    fn my_is_nan(&self) -> bool;
}

#[duplicate_item(
    num_type is_nan_impl;
    [bool] [false];
    [u8] [false];
    [u16] [false];
    [u32] [false];
    [u64] [false];
    [i8] [false];
    [i16] [false];
    [i32] [false];
    [i64] [false];
    [f32] [self.is_nan()];
    [f64] [self.is_nan()];
)]
impl IsNan for num_type {
    fn my_is_nan(&self) -> bool {
        is_nan_impl
    }
}

pub trait MyToPrimitive {
    fn my_to_f64(&self) -> Option<f64>;
}

#[duplicate_item(
    num_type to_f64_impl;
    [bool] [if *self { Some(1.0) } else { Some(0.0) }];
    [u8] [Some((*self).to_f64().unwrap())];
    [u16] [Some((*self).to_f64().unwrap())];
    [u32] [Some((*self).to_f64().unwrap())];
    [u64] [Some((*self).to_f64().unwrap())];
    [i8] [Some((*self).to_f64().unwrap())];
    [i16] [Some((*self).to_f64().unwrap())];
    [i32] [Some((*self).to_f64().unwrap())];
    [i64] [Some((*self).to_f64().unwrap())];
    [f32] [Some((*self).to_f64().unwrap())];
    [f64] [Some((*self).to_f64().unwrap())];
)]
impl MyToPrimitive for num_type {
    fn my_to_f64(&self) -> Option<f64> {
        to_f64_impl
    }
}

pub fn format_integer_with_underscore(num: u64) -> String {
    let num_str = num.to_string();
    let mut formatted = String::new();
    let len = num_str.len();

    for (i, c) in num_str.chars().enumerate() {
        if i > 0 && (len - i) % 3 == 0 {
            formatted.push('_');
        }
        formatted.push(c);
    }

    formatted
}

pub fn file_size_fmt_no_scale(size: u64) -> String {
    format_integer_with_underscore(size) + " B"
}

pub fn file_size_fmt(size: u64) -> String {
    if size < 1024 {
        return format!("{} B", size);
    }

    let mut f: Formatter = Formatter::new()
        .scales(Scales::metric())
        .precision(Precision::Decimals(3))
        .suffix("B")
        .unwrap();

    f.fmt2(size).to_owned()
}

pub fn large_int_fmt(size: u64) -> String {
    if size < 1000 {
        return format!("{}", size);
    }

    let mut f: Formatter = Formatter::new()
        .scales(Scales::metric())
        .precision(Precision::Decimals(1));

    f.fmt2(size).to_owned()
}

pub fn basic_float_fmt(x: f32) -> String {
    let mut f: Formatter = Formatter::new().precision(Precision::Significance(3));

    f.fmt2(x).to_owned()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_file_size_fmt() {
        assert_eq!(file_size_fmt(1u64), "1 B");
        assert_eq!(file_size_fmt(999u64), "999 B");
        assert_eq!(file_size_fmt(1024u64), "1.024 kB");
        assert_eq!(file_size_fmt(123123123123), "123.123 GB");
        assert_eq!(file_size_fmt(5123123123123), "5.123 TB");
        assert_eq!(file_size_fmt_no_scale(123123123123), "123_123_123_123 B");

        assert_eq!(large_int_fmt(1u64), "1");
        assert_eq!(large_int_fmt(999u64), "999");
        assert_eq!(large_int_fmt(1000u64), "1.0 k");
        assert_eq!(large_int_fmt(98701928390123), "98.7 T");
    }
}