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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
//! # **Humansize**
//!
//! Humansize lets you easily represent file sizes in a human-friendly format.
//! You can specify your own formatting style, pick among the three defaults provided
//! by the library:
//! 
//! * Decimal (Multiples of 1000, `KB` units)
//! * Binary (Multiples of 1024, `KiB` units)
//! * Conventional (Multiples of 1024, `KB` units)
//!
//! ## How to use it
//!
//! Simply import the `FileSize` trait and the options module and call the
//! file_size method on any positive integer, using one of the three standards
//! provided by the options module.
//!
//! ```rust
//! extern crate humansize;
//! use humansize::{FileSize, file_size_opts as options};
//! 
//! fn main() {
//! 	let size = 1000;
//! 	println!("Size is {}", size.file_size(options::DECIMAL).unwrap());
//!	
//! 	println!("Size is {}", size.file_size(options::BINARY).unwrap());
//!	
//! 	println!("Size is {}", size.file_size(options::CONVENTIONAL).unwrap());
//! }
//! ```
//!
//! If you wish to customize the way sizes are displayed, you may create your own custom `FileSizeOpts` struct
//! and pass that to the method. See the `custom_options.rs` file in the example folder.

static SCALE_DECIMAL: [&'static str; 9] = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
static SCALE_DECIMAL_LONG: [&'static str; 9] = ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes", "Petabytes", "Exabytes", "Zettabytes", "Yottabytes"];

static SCALE_BINARY: [&'static str; 9] = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
static SCALE_BINARY_LONG: [&'static str; 9] = ["Bytes", "Kibibytes", "Mebibytes", "Gibibytes", "Tebibytes", "Pebibytes", "Exbibytes", "Zebibytes", "Yobibytes"];


pub mod file_size_opts {
	//! Describes the struct that holds the options needed by the `file_size` method.
	//! The three common formats are provided as constants to be used easily


    #[derive(PartialEq)]
    /// Holds the standard to use when displying the size.
    pub enum Standard {
    	/// The decimal scale and units
        Decimal,
        /// The binary scale and units
        Binary,
    }

    /// Holds the options to be passed to the `file_size` method.
    pub struct FileSizeOpts {
    	/// The scale to compare the size against.
        pub divider: Standard,
        /// The unit set to use.
        pub units: Standard,
        /// The amount of places if the decimal part is non-zero.
        pub decimal_places: usize,
        /// The amount of zeroes to display of the decimal part is zero.
        pub decimal_zeroes: usize,
        /// Wether to use the full suffix or its abbreveation.
        pub long_suffix: bool
    }

    /// Options to display sizes in the binary format
    pub const BINARY: FileSizeOpts = FileSizeOpts {
        divider: Standard::Binary,
        units: Standard::Binary,
        decimal_places: 2,
        decimal_zeroes: 0,
        long_suffix: false
    };

    /// Options to display sizes in the decimal format
    pub const DECIMAL: FileSizeOpts = FileSizeOpts {
        divider: Standard::Decimal,
        units: Standard::Decimal,
        decimal_places: 2,
        decimal_zeroes: 0,
        long_suffix: false
    };

    /// Options to display sizes in the conventional format.Standard
    /// This uses multiples of 1024 to calculate the scale, but displays decimal units (`KB`, not `KiB`).
    pub const CONVENTIONAL: FileSizeOpts = FileSizeOpts {
        divider: Standard::Binary,
        units: Standard::Decimal,
        decimal_places: 2,
        decimal_zeroes: 0,
        long_suffix: false
    };
}
/// The trait for the `file_size`method
pub trait FileSize {
	/// Formats self according to the parameters in `opts`. `opts` can either be one of the 
	/// three defaults providedby the `file_size_opts` module, or be custom-defined according
	/// to your needs
	///
	/// # Errors
	/// Will fail if called on a negative number
	///
	/// # Examples
	/// ```rust
	/// use humansize::{FileSize, file_size_opts as options};
	///
	/// let size = 5128;
	/// println!("Size is {}", size.file_size(options::DECIMAL).unwrap());
	/// ```
	///
	fn file_size(&self, opts: FileSizeOpts) -> Result<String, String>;
}

use self::file_size_opts::*;

macro_rules! impl_file_size_u {
    (for $($t:ty)*) => ($(
        impl FileSize for $t {
        	fn file_size(&self, opts: FileSizeOpts) -> Result<String, String> {
        		let divider = match opts.divider {
        			Standard::Decimal => 1000.0,
        			Standard::Binary => 1024.0
    			};
			
    			let mut size: f64 = *self as f64;
    			let mut scale_idx = 0;
			
    			while size >= divider {
    			    size /= divider;
    				scale_idx += 1;
    			}
			
    			let mut scale = match (opts.units, opts.long_suffix) {
    				(Standard::Decimal, false) => SCALE_DECIMAL[scale_idx],
    				(Standard::Decimal, true) => SCALE_DECIMAL_LONG[scale_idx],
    				(Standard::Binary, false) => SCALE_BINARY[scale_idx],
    				(Standard::Binary, true) => SCALE_BINARY_LONG[scale_idx]
    			};

    			if opts.long_suffix && size.trunc() == 1.0 { scale = &scale[0 .. scale.len()-1];}
			
    			let places = match size.fract() {
    				0.0 => opts.decimal_zeroes,
    				_ => opts.decimal_places
    			};
			
    			Ok(format!("{:.*} {}", places, size, scale))
    		}
	    }
    )*)
}

macro_rules! impl_file_size_i {
    (for $($t:ty)*) => ($(
        impl FileSize for $t {
        	fn file_size(&self, opts: FileSizeOpts) -> Result<String, String> {
        		if *self < 0 {return Err("Tried calling file_size on a negative value".to_owned());}
        		(*self as u64).file_size(opts)

    		}
	    }
    )*)
}

impl_file_size_u!(for usize u8 u16 u32 u64);
impl_file_size_i!(for isize i8 i16 i32 i64);


#[test]
fn test_sizes() {
	assert_eq!(0.file_size(BINARY).unwrap(), "0 B");
	assert_eq!(999.file_size(BINARY).unwrap(), "999 B");
	assert_eq!(1000.file_size(BINARY).unwrap(), "1000 B");
	assert_eq!(1000.file_size(DECIMAL).unwrap(), "1 KB");
	assert_eq!(1023.file_size(BINARY).unwrap(), "1023 B");
	assert_eq!(1023.file_size(DECIMAL).unwrap(), "1.02 KB");
	assert_eq!(1024.file_size(BINARY).unwrap(), "1 KiB");
	assert_eq!(1024.file_size(CONVENTIONAL).unwrap(), "1 KB");
}