1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
//! 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::Cell;
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<Cell<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<Cell<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().get().base {
            BytesBase::Gabyte => 1000,
            BytesBase::Bibyte => 1024,
        };

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

        // Obtain padding from associated BytesBuilder
        let aligned: bool = (*self.config).lock().unwrap().get().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)
    }
}