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
//! This crate provides a simple `Bytes` struct that stores a number
//! and displays it in as the appropriate multiple of a power of  1000 bytes,
//! i.e. in Megabytes, Gigabyte, etc. for human readability.
//!
//! Example:
//!
//! ```rust
//! let b = 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.
//!

mod test;

use std::cmp::PartialOrd;
use std::fmt::{Display, Formatter, Result};

/// 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 {}

/// The actual struct.
/// 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(Debug, PartialEq, PartialOrd)]
pub struct Bytes<U: Unsigned + Display + PartialOrd + Into<u64> + Copy>(pub U);

impl<U: Unsigned + Display + PartialOrd + Into<u64> + Copy> Bytes<U> {
    // TODO arithmetics
}

impl<U> Display for Bytes<U>
where
    U: Unsigned + Display + PartialOrd + Into<u64> + Copy,
{
    fn fmt(&self, f: &mut Formatter) -> Result {
        const BASE: u64 = 1000;
        // 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.0.into();

        // Bytes
        if b < BASE {
            return write!(f, "{} B", self.0);
        }
        // Kilobytes
        // We subtract the number of bytes needed to get to the point where the value
        // (rounded at the second decimal) jumps to the next power of 10
        // (e.g. 999.99 MB -> 1.00 GB at 1,000,000 - 5 = 999,995)
        else if b < BASE.pow(2) - 5 {
            let divisor = BASE;
            let quotient = b / divisor;
            let remainder = b % divisor;
            // We are doing the division in integer to avoid having to store the full
            // value in f64 and risk overflow (u64 does not implement Into<f64>).
            let mut result: f64 = quotient as f64 + remainder as f64 / divisor as f64;
            result = (result * 100.0).round() / 100.0;
            return write!(f, "{:.2} KB", result);
        }
        // Megabytes
        else if b < BASE.pow(3) - 5 * BASE {
            let divisor = BASE.pow(2);
            let quotient = b / divisor;
            let remainder = b % divisor;
            let mut result: f64 = quotient as f64 + remainder as f64 / divisor as f64;
            result = (result * 100.0).round() / 100.0;
            return write!(f, "{:.2} MB", result);
        }
        // Gigabytes
        else if b < BASE.pow(4) - 5 * BASE.pow(2) {
            let divisor = BASE.pow(3);
            let quotient = b / divisor;
            let remainder = b % divisor;
            let mut result: f64 = quotient as f64 + remainder as f64 / divisor as f64;
            result = (result * 100.0).round() / 100.0;
            return write!(f, "{:.2} GB", result);
        }
        // Terabytes
        else if b < BASE.pow(5) - 5 * BASE.pow(3) {
            let divisor = BASE.pow(4);
            let quotient = b / divisor;
            let remainder = b % divisor;
            let mut result: f64 = quotient as f64 + remainder as f64 / divisor as f64;
            result = (result * 100.0).round() / 100.0;
            return write!(f, "{:.2} TB", result);
        }
        // Petabytes
        // Anything larger will be displayed as petabytes
        else {
            let divisor = BASE.pow(5);
            let quotient = b / divisor;
            let remainder = b % divisor;
            let mut result: f64 = quotient as f64 + remainder as f64 / divisor as f64;
            result = (result * 100.0).round() / 100.0;
            return write!(f, "{:.2} PB", result);
        }
    }
}