gabi 0.2.6

This crate provides a simple `Bytes` struct that stores a number and displays it in human readable format
Documentation
//! This crate provides a mechanism to store numbers and display them
//! as a multiple of the chosen power of 1000 bytes or 1024 bytes,
//! e.g. in Megabytes or Mebibytes for human readability.
//!
//! Example:
//!
//! ```rust
//! let mut bb = BytesConfig::default();
//! let b = bb.bytes(5247 as u16);
//! println!("{}", b);  //  Prints "5.25 KB"
//! ```
//!
//! The number is stored internally in the same type as was provided to initialize
//! the struct: `u16` in this example.
//!
//! The `Bytes` structs are displayed using the preferences held in the `BytesConfig` struct
//! that created them:
//!
//! ```rust
//! bb.set_precision(1);
//! println!("{}", b);  //  Prints "5.3 KB"
//! ```
//!
//! See example for more details.

use std::cell::RefCell;
use std::fmt::{Display, Formatter, Result};
use std::sync::{Arc, Mutex};

// Re-export contents of bytesbuilder module
mod config;
pub use config::*;

mod test;

/// Marker trait used to indicate to the compiler what types are allowed to initialize the struct.
pub trait Unsigned {}

impl Unsigned for u8 {}
impl Unsigned for u16 {}
impl Unsigned for u32 {}
impl Unsigned for u64 {}

// Arrays with units to display for each power of the base being used.
/// Array with the list of unit names to use in the power of 1000 system
pub const GABYTES: [&str; 7] = ["B", "KB", "MB", "GB", "TB", "PB", "EB"];
/// Array with the list of unit names to use in the power of 1024 system
pub const BIBYTES: [&str; 7] = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"];

/// The struct used to store the number of bytes.
/// Trait bounds specify what traits are required from the type used to store the number of bytes.
/// `Into<u64>` in particular is required as the number of bytes is temporarily converted to `u64`
/// (the only type that we are sure can store any number stored in another type without overflowing)
/// to compare it.
#[derive(Default, Clone)]
pub struct Bytes<U: Unsigned + Display + PartialOrd + Into<u64> + Copy> {
    config: Arc<Mutex<RefCell<BytesParam>>>,
    bytes: U,
}

impl<U: Unsigned + Display + PartialOrd + Into<u64> + Copy> Bytes<U> {
    /// Used by BytesConfig struct to instanciate Byte
    fn new(config: Arc<Mutex<RefCell<BytesParam>>>, byte: U) -> Self {
        Self {
            config,
            bytes: byte,
        }
    }

    /// Return a copy of the value in the type that was used to store it internally
    pub fn value(&self) -> U {
        self.bytes.clone()
    }

    /// Return value in the type that was used to store it internally and consume Byte
    pub fn into_inner(self) -> U {
        self.bytes
    }

    // TODO Arithmetics
    // TODO Override BytesConfig preferences
}

// implementation of the Display trait used by the `println!()` macro for instance.
impl<U> Display for Bytes<U>
where
    U: Unsigned + Display + PartialOrd + Into<u64> + Copy,
{
    fn fmt(&self, f: &mut Formatter) -> Result {
        // Obtain base from associated BytesBuilder
        let base: u64 = match (*self.config).lock().unwrap().borrow().base {
            BytesBase::Gabyte => 1000,
            BytesBase::Bibyte => 1024,
        };

        // Obtain number of decimals to display from associated BytesBuilder
        let precision: usize = (*self.config).lock().unwrap().borrow().precision;
        let prec_factor: f64 = 10.0_f64.powi(precision as i32);

        // Obtain padding from associated BytesBuilder
        let aligned: bool = (*self.config).lock().unwrap().borrow().aligned;

        // Convert value into known type (u64 is the only one that can fit all others)
        // because we can't do comparisons with generic types.
        let b: u64 = self.bytes.into();

        for e in 0_u32..7_u32 {
            let divisor = base.pow(e);

            // Divide value by upper limit of unit
            let quotient = b / divisor;
            let remainder = b % divisor;
            let mut result: f64 = quotient as f64 + remainder as f64 / divisor as f64;

            // Round result to the specified precision
            result = (result * prec_factor).round() / prec_factor;

            // If we are looking at bytes
            if e == 0 {
                // If result value is in the correct range
                if result < base as f64 {
                    let unit = if base == 1000 {
                        GABYTES[e as usize]
                    } else {
                        BIBYTES[e as usize]
                    };
                    // Display value without decimal zeros (e.g. not "235.00 B" but "235 B")
                    if aligned {
                        let width = 4 + precision;
                        return write!(f, "{:width$} {}", result, unit);
                    } else {
                        return write!(f, "{:.0} {}", result as usize, unit);
                    }
                }
            } else {
                // if result value is in the correct range
                if result < base as f64 {
                    let unit = if base == 1000 {
                        GABYTES[e as usize]
                    } else {
                        BIBYTES[e as usize]
                    };
                    if aligned {
                        // Aligned to maximum possible width wich is width of "999." + precision
                        let width = 4 + precision;
                        return write!(f, "{:width$.precision$} {}", result, unit);
                    } else {
                        return write!(f, "{:.precision$} {}", result, unit);
                    }
                }
            }
        }
        // Could not format value (should never happen)
        Err(std::fmt::Error)
    }
}