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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#[cfg(test)]
use clap::App;
use clap::ArgMatches;
extern crate colored;
use self::colored::*;
use std::collections::BTreeMap;
use std::env;
use std::io;
#[allow(unused_imports)] // method write_all is needed
use std::io::Write;
use std::iter::FromIterator;

/// Report
///
/// Send report to stdout
pub fn report(disk_space: BTreeMap<String, u64>, matches: &ArgMatches) {
    report_stream(&mut io::stdout(), disk_space, matches)
}

/// Report_Stream
///
/// Sort the entries by size and output the top 20
#[allow(unused_must_use)]
pub fn report_stream(out: &mut io::Write, disk_space: BTreeMap<String, u64>, matches: &ArgMatches) {
    let mut sorted = Vec::from_iter(disk_space);
    let end = if matches.occurrences_of("all") == 0 && sorted.len() > 20 {
        20
    } else {
        sorted.len()
    };

    let section = if matches.occurrences_of("reverse") == 0 {
        sorted.sort_by(|&(_, a), &(_, b)| b.cmp(&a));
        &sorted[0..end]
    } else {
        sorted.sort_by(|&(_, a), &(_, b)| a.cmp(&b));
        &sorted[(sorted.len() - end)..]
    };

    for &(ref filename, size) in section {
        writeln!(out, "{} {}", color(size, matches), filename);
    }
}

/// Color
///
/// Returns a string that will contain colored unit output if the
/// TERM environment variable is set.  Defaults to yellow on Linux and
/// cyan on Windows(cygwin).  Color preference specified as a command
/// line option.
fn color(number: u64, matches: &ArgMatches) -> String {
    match env::var_os("TERM") {
        None => simple_units(number),
        Some(term) => match term.as_os_str().to_str().unwrap() {
            "cygwin" => simple_units(number).cyan().bold().to_string(),
            _ => match matches.value_of("color") {
                Some("black") => simple_units(number).black().bold().to_string(),
                Some("red") => simple_units(number).red().bold().to_string(),
                Some("green") => simple_units(number).green().bold().to_string(),
                Some("yellow") => simple_units(number).yellow().bold().to_string(),
                Some("blue") => simple_units(number).blue().bold().to_string(),
                Some("magenta") => simple_units(number).magenta().bold().to_string(),
                Some("cyan") => simple_units(number).cyan().bold().to_string(),
                Some("white") => simple_units(number).white().bold().to_string(),
                Some("none") => simple_units(number),
                _ => simple_units(number).yellow().bold().to_string(),
            },
        },
    }
}

/// Simple_Units
///
/// Convert number to human friendly format
fn simple_units(number: u64) -> String {
    let units = [" ", "K", "M", "G", "T", "P"];
    let index: usize = (number as f64).log(1024.0).trunc() as usize;
    let n = number / 1024u64.pow(index as u32);

    if index == 0 || index > 5 {
        format!("{:>6}", n)
    } else {
        format!("{:>5}{}", n, units[index])
    }
}

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

    #[cfg(target_os = "linux")]
    #[test]
    fn report_short() {
        let mut data = BTreeMap::new();
        data.insert("path/to/fileA".to_string(), 2048 as u64);
        data.insert("path/to/fileB".to_string(), 1024 as u64);

        let mut out = Vec::new();
        let matches = App::new("DiskSpace").get_matches();
        report_stream(&mut out, data, &matches);
        assert_eq!(
            out,
            format!(
                "{} path/to/fileA\n{} path/to/fileB\n",
                "    2K".yellow().bold(),
                "    1K".yellow().bold()
            )
            .as_bytes()
        )
    }

    #[cfg(target_os = "linux")]
    #[test]
    fn report_long() {
        let mut data = BTreeMap::new();
        data.insert("path/to/fileA".to_string(), 2048 as u64);
        data.insert("path/to/fileB".to_string(), 1024 as u64);
        data.insert("path/to/fileC".to_string(), 1023 as u64);
        data.insert("path/to/fileD".to_string(), 1022 as u64);
        data.insert("path/to/fileE".to_string(), 1021 as u64);
        data.insert("path/to/fileF".to_string(), 1020 as u64);
        data.insert("path/to/fileG".to_string(), 1019 as u64);
        data.insert("path/to/fileH".to_string(), 1018 as u64);
        data.insert("path/to/fileI".to_string(), 1017 as u64);
        data.insert("path/to/fileJ".to_string(), 1016 as u64);
        data.insert("path/to/fileK".to_string(), 1015 as u64);
        data.insert("path/to/fileL".to_string(), 1014 as u64);
        data.insert("path/to/fileM".to_string(), 1013 as u64);
        data.insert("path/to/fileN".to_string(), 1012 as u64);
        data.insert("path/to/fileO".to_string(), 1011 as u64);
        data.insert("path/to/fileP".to_string(), 1010 as u64);
        data.insert("path/to/fileQ".to_string(), 1009 as u64);
        data.insert("path/to/fileR".to_string(), 1008 as u64);
        data.insert("path/to/fileS".to_string(), 1007 as u64);
        data.insert("path/to/fileT".to_string(), 1006 as u64);
        data.insert("path/to/fileU".to_string(), 1005 as u64);

        let mut out = Vec::new();
        let matches = App::new("DiskSpace").get_matches();
        report_stream(&mut out, data, &matches);
        assert_eq!(
            out,
            format!(
                "{} path/to/fileA
{} path/to/fileB
{} path/to/fileC
{} path/to/fileD
{} path/to/fileE
{} path/to/fileF
{} path/to/fileG
{} path/to/fileH
{} path/to/fileI
{} path/to/fileJ
{} path/to/fileK
{} path/to/fileL
{} path/to/fileM
{} path/to/fileN
{} path/to/fileO
{} path/to/fileP
{} path/to/fileQ
{} path/to/fileR
{} path/to/fileS
{} path/to/fileT
",
                "    2K".yellow().bold(),
                "    1K".yellow().bold(),
                "  1023".yellow().bold(),
                "  1022".yellow().bold(),
                "  1021".yellow().bold(),
                "  1020".yellow().bold(),
                "  1019".yellow().bold(),
                "  1018".yellow().bold(),
                "  1017".yellow().bold(),
                "  1016".yellow().bold(),
                "  1015".yellow().bold(),
                "  1014".yellow().bold(),
                "  1013".yellow().bold(),
                "  1012".yellow().bold(),
                "  1011".yellow().bold(),
                "  1010".yellow().bold(),
                "  1009".yellow().bold(),
                "  1008".yellow().bold(),
                "  1007".yellow().bold(),
                "  1006".yellow().bold()
            )
            .as_bytes()
        )
    }

    #[test]
    fn simple_units_bytes() {
        assert_eq!(simple_units(100), "   100");
    }

    #[test]
    fn simple_units_kbytes() {
        assert_eq!(simple_units(1025), "    1K");
    }

    #[test]
    fn simple_units_kbytes_long() {
        assert_eq!(simple_units(1025000), " 1000K");
    }

    #[test]
    fn simple_units_mbytes() {
        assert_eq!(simple_units(2_200_000), "    2M");
    }

}