1#[cfg(test)]
2use clap::App;
3use clap::ArgMatches;
4extern crate colored;
5use self::colored::*;
6use std::collections::BTreeMap;
7use std::env;
8use std::io;
9#[allow(unused_imports)] use std::io::Write;
11use std::iter::FromIterator;
12
13pub struct ReportSettings {
14 pub all: bool,
15 pub reverse: bool,
16 pub lines: usize,
17 pub exclude: Vec<String>,
18}
19
20impl ReportSettings {
21 pub fn new() -> ReportSettings {
22 ReportSettings {
23 all: false,
24 reverse: false,
25 lines: 20,
26 exclude: Vec::new(),
27 }
28 }
29
30 pub fn settings(&mut self, matches: &ArgMatches) {
31 self.all = matches.occurrences_of("all") > 0;
32 self.reverse = matches.occurrences_of("reverse") > 0;
33
34 if let Some(lines) = matches.value_of("lines") {
35 self.lines = match lines.to_string().parse() {
36 Err(err) => {
37 eprintln!("Check lines option: {}", err);
38 self.lines
39 }
40 Ok(lines) => lines,
41 }
42 }
43
44 if let Some(exclude) = matches.values_of("exclude") {
45 self.exclude = exclude.map(|x| x.to_string()).collect();
46 }
47 }
48}
49
50pub fn report(disk_space: BTreeMap<String, u64>, matches: &ArgMatches) {
54 report_stream(&mut io::stdout(), disk_space, matches)
55}
56
57#[allow(unused_must_use)]
61pub fn report_stream(
62 out: &mut io::Write,
63 mut disk_space: BTreeMap<String, u64>,
64 matches: &ArgMatches,
65) {
66 let mut rs = ReportSettings::new();
67 rs.settings(matches);
68 if !rs.exclude.is_empty() {
69 disk_space = exclude(&rs, disk_space);
70 }
71
72 let mut unsorted = Vec::from_iter(disk_space);
73 let end = endpoint(&rs, unsorted.len());
74
75 let sorted = if rs.reverse {
76 unsorted.sort_by(|&(_, a), &(_, b)| a.cmp(&b));
77 &unsorted[(unsorted.len() - end)..]
78 } else {
79 unsorted.sort_by(|&(_, a), &(_, b)| b.cmp(&a));
80 &unsorted[0..end]
81 };
82
83 for &(ref filename, size) in sorted {
84 writeln!(out, "{} {}", color(size, matches), filename);
85 }
86}
87
88fn endpoint(rs: &ReportSettings, length: usize) -> usize {
89 if !rs.all && length > rs.lines {
90 rs.lines
91 } else {
92 length
93 }
94}
95
96fn exclude(rs: &ReportSettings, disk_space: BTreeMap<String, u64>) -> BTreeMap<String, u64> {
97 let mut tmp = BTreeMap::new();
98 let mut include = true;
99 for filename in disk_space.keys() {
100 for exclusion in &rs.exclude {
101 if filename.contains(exclusion) {
102 include = false;
103 break;
104 }
105 }
106 if include {
107 tmp.insert(filename.to_string(), *disk_space.get(filename).unwrap());
108 } else {
109 include = true;
110 }
111 }
112 tmp
113}
114
115fn color(number: u64, matches: &ArgMatches) -> String {
122 match env::var_os("TERM") {
123 None => simple_units(number),
124 Some(term) => match term.as_os_str().to_str().unwrap() {
125 "cygwin" => simple_units(number).cyan().bold().to_string(),
126 _ => match matches.value_of("color") {
127 Some("black") => simple_units(number).black().bold().to_string(),
128 Some("red") => simple_units(number).red().bold().to_string(),
129 Some("green") => simple_units(number).green().bold().to_string(),
130 Some("yellow") => simple_units(number).yellow().bold().to_string(),
131 Some("blue") => simple_units(number).blue().bold().to_string(),
132 Some("magenta") => simple_units(number).magenta().bold().to_string(),
133 Some("cyan") => simple_units(number).cyan().bold().to_string(),
134 Some("white") => simple_units(number).white().bold().to_string(),
135 Some("none") => simple_units(number),
136 _ => simple_units(number).yellow().bold().to_string(),
137 },
138 },
139 }
140}
141
142fn simple_units(number: u64) -> String {
146 let units = [" ", "K", "M", "G", "T", "P"];
147 let index: usize = (number as f64).log(1024.0).trunc() as usize;
148 let n = number / 1024u64.pow(index as u32);
149
150 if index == 0 || index > 5 {
151 format!("{:>6}", n)
152 } else {
153 format!("{:>5}{}", n, units[index])
154 }
155}
156
157#[cfg(test)]
158#[allow(unused_must_use)]
159mod tests {
160 use super::*;
161 use clap::Arg;
162 use std::env;
163
164 #[cfg(target_os = "linux")]
165 #[test]
166 fn report_short() {
167 let mut data = BTreeMap::new();
168 data.insert("path/to/fileA".to_string(), 2048 as u64);
169 data.insert("path/to/fileB".to_string(), 1024 as u64);
170
171 let mut out = Vec::new();
172 let matches = App::new("DiskSpace").get_matches();
173 report_stream(&mut out, data, &matches);
174 assert_eq!(
175 out,
176 format!(
177 "{} path/to/fileA\n{} path/to/fileB\n",
178 " 2K".yellow().bold(),
179 " 1K".yellow().bold()
180 )
181 .as_bytes()
182 )
183 }
184
185 #[cfg(target_os = "linux")]
186 #[test]
187 fn report_short_reverse() {
188 let mut data = BTreeMap::new();
189 data.insert("path/to/fileA".to_string(), 2048 as u64);
190 data.insert("path/to/fileB".to_string(), 1024 as u64);
191
192 let mut out = Vec::new();
193 let args = vec!["ds", "-r"];
194 let matches = App::new("DiskSpace")
195 .arg(Arg::with_name("reverse").short("r"))
196 .get_matches_from(args);
197 report_stream(&mut out, data, &matches);
198 assert_eq!(
199 out,
200 format!(
201 "{} path/to/fileB\n{} path/to/fileA\n",
202 " 1K".yellow().bold(),
203 " 2K".yellow().bold(),
204 )
205 .as_bytes()
206 )
207 }
208
209 #[cfg(target_os = "linux")]
210 #[test]
211 fn report_short_exclude() {
212 let mut data = BTreeMap::new();
213 data.insert("path/to/fileA".to_string(), 2048 as u64);
214 data.insert("path/to/fileB".to_string(), 1024 as u64);
215
216 let mut out = Vec::new();
217 let args = vec!["ds", "-e", "fileB"];
218 let matches = App::new("DiskSpace")
219 .arg(
220 Arg::with_name("exclude")
221 .short("e")
222 .min_values(1)
223 .multiple(true),
224 )
225 .get_matches_from(args);
226 report_stream(&mut out, data, &matches);
227 assert_eq!(
228 out,
229 format!("{} path/to/fileA\n", " 2K".yellow().bold(),).as_bytes()
230 )
231 }
232
233 #[cfg(target_os = "linux")]
234 #[test]
235 fn report_stdout() {
236 let data = BTreeMap::new();
237 let matches = App::new("DiskSpace").get_matches();
238 report(data, &matches);
239 }
240
241 #[cfg(target_os = "linux")]
242 #[test]
243 fn report_long() {
244 let mut data = BTreeMap::new();
245 data.insert("path/to/fileA".to_string(), 2048 as u64);
246 data.insert("path/to/fileB".to_string(), 1024 as u64);
247 data.insert("path/to/fileC".to_string(), 1023 as u64);
248 data.insert("path/to/fileD".to_string(), 1022 as u64);
249 data.insert("path/to/fileE".to_string(), 1021 as u64);
250 data.insert("path/to/fileF".to_string(), 1020 as u64);
251 data.insert("path/to/fileG".to_string(), 1019 as u64);
252 data.insert("path/to/fileH".to_string(), 1018 as u64);
253 data.insert("path/to/fileI".to_string(), 1017 as u64);
254 data.insert("path/to/fileJ".to_string(), 1016 as u64);
255 data.insert("path/to/fileK".to_string(), 1015 as u64);
256 data.insert("path/to/fileL".to_string(), 1014 as u64);
257 data.insert("path/to/fileM".to_string(), 1013 as u64);
258 data.insert("path/to/fileN".to_string(), 1012 as u64);
259 data.insert("path/to/fileO".to_string(), 1011 as u64);
260 data.insert("path/to/fileP".to_string(), 1010 as u64);
261 data.insert("path/to/fileQ".to_string(), 1009 as u64);
262 data.insert("path/to/fileR".to_string(), 1008 as u64);
263 data.insert("path/to/fileS".to_string(), 1007 as u64);
264 data.insert("path/to/fileT".to_string(), 1006 as u64);
265 data.insert("path/to/fileU".to_string(), 1005 as u64);
266
267 let mut out = Vec::new();
268 let matches = App::new("DiskSpace").get_matches();
269 report_stream(&mut out, data, &matches);
270 assert_eq!(
271 out,
272 format!(
273 "{} path/to/fileA
274{} path/to/fileB
275{} path/to/fileC
276{} path/to/fileD
277{} path/to/fileE
278{} path/to/fileF
279{} path/to/fileG
280{} path/to/fileH
281{} path/to/fileI
282{} path/to/fileJ
283{} path/to/fileK
284{} path/to/fileL
285{} path/to/fileM
286{} path/to/fileN
287{} path/to/fileO
288{} path/to/fileP
289{} path/to/fileQ
290{} path/to/fileR
291{} path/to/fileS
292{} path/to/fileT
293",
294 " 2K".yellow().bold(),
295 " 1K".yellow().bold(),
296 " 1023".yellow().bold(),
297 " 1022".yellow().bold(),
298 " 1021".yellow().bold(),
299 " 1020".yellow().bold(),
300 " 1019".yellow().bold(),
301 " 1018".yellow().bold(),
302 " 1017".yellow().bold(),
303 " 1016".yellow().bold(),
304 " 1015".yellow().bold(),
305 " 1014".yellow().bold(),
306 " 1013".yellow().bold(),
307 " 1012".yellow().bold(),
308 " 1011".yellow().bold(),
309 " 1010".yellow().bold(),
310 " 1009".yellow().bold(),
311 " 1008".yellow().bold(),
312 " 1007".yellow().bold(),
313 " 1006".yellow().bold()
314 )
315 .as_bytes()
316 )
317 }
318
319 #[test]
320 fn simple_units_bytes() {
321 assert_eq!(simple_units(100), " 100");
322 }
323
324 #[test]
325 fn simple_units_kbytes() {
326 assert_eq!(simple_units(1025), " 1K");
327 }
328
329 #[test]
330 fn simple_units_kbytes_long() {
331 assert_eq!(simple_units(1025000), " 1000K");
332 }
333
334 #[test]
335 fn simple_units_mbytes() {
336 assert_eq!(simple_units(2_200_000), " 2M");
337 }
338
339 #[test]
340 fn color_black() {
341 let args = vec!["ds", "-c", "black"];
342 let matches = App::new("DiskSpace")
343 .arg(
344 Arg::with_name("color")
345 .short("c")
346 .value_name("COLOR")
347 .takes_value(true),
348 )
349 .get_matches_from(args);
350 env::set_var("TERM", "xterm-256color");
351
352 let result = color(10, &matches);
353 assert_eq!(result, " 10".black().bold().to_string());
354 }
355
356 #[test]
357 fn color_red() {
358 let args = vec!["ds", "-c", "red"];
359 let matches = App::new("DiskSpace")
360 .arg(
361 Arg::with_name("color")
362 .short("c")
363 .value_name("COLOR")
364 .takes_value(true),
365 )
366 .get_matches_from(args);
367 env::set_var("TERM", "xterm-256color");
368
369 let result = color(10, &matches);
370 assert_eq!(result, " 10".red().bold().to_string());
371 }
372
373 #[test]
374 fn color_green() {
375 let args = vec!["ds", "-c", "green"];
376 let matches = App::new("DiskSpace")
377 .arg(
378 Arg::with_name("color")
379 .short("c")
380 .value_name("COLOR")
381 .takes_value(true),
382 )
383 .get_matches_from(args);
384 env::set_var("TERM", "xterm-256color");
385
386 let result = color(10, &matches);
387 assert_eq!(result, " 10".green().bold().to_string());
388 }
389
390 #[test]
391 fn color_yellow() {
392 let args = vec!["ds", "-c", "yellow"];
393 let matches = App::new("DiskSpace")
394 .arg(
395 Arg::with_name("color")
396 .short("c")
397 .value_name("COLOR")
398 .takes_value(true),
399 )
400 .get_matches_from(args);
401 env::set_var("TERM", "xterm-256color");
402
403 let result = color(10, &matches);
404 assert_eq!(result, " 10".yellow().bold().to_string());
405 }
406
407 #[test]
408 fn color_blue() {
409 let args = vec!["ds", "-c", "blue"];
410 let matches = App::new("DiskSpace")
411 .arg(
412 Arg::with_name("color")
413 .short("c")
414 .value_name("COLOR")
415 .takes_value(true),
416 )
417 .get_matches_from(args);
418 env::set_var("TERM", "xterm-256color");
419
420 let result = color(10, &matches);
421 assert_eq!(result, " 10".blue().bold().to_string());
422 }
423
424 #[test]
425 fn color_magenta() {
426 let args = vec!["ds", "-c", "magenta"];
427 let matches = App::new("DiskSpace")
428 .arg(
429 Arg::with_name("color")
430 .short("c")
431 .value_name("COLOR")
432 .takes_value(true),
433 )
434 .get_matches_from(args);
435 env::set_var("TERM", "xterm-256color");
436
437 let result = color(10, &matches);
438 assert_eq!(result, " 10".magenta().bold().to_string());
439 }
440
441 #[test]
442 fn color_cyan() {
443 let args = vec!["ds", "-c", "cyan"];
444 let matches = App::new("DiskSpace")
445 .arg(
446 Arg::with_name("color")
447 .short("c")
448 .value_name("COLOR")
449 .takes_value(true),
450 )
451 .get_matches_from(args);
452 env::set_var("TERM", "xterm-256color");
453
454 let result = color(10, &matches);
455 assert_eq!(result, " 10".cyan().bold().to_string());
456 }
457
458 #[test]
459 fn color_white() {
460 let args = vec!["ds", "-c", "white"];
461 let matches = App::new("DiskSpace")
462 .arg(
463 Arg::with_name("color")
464 .short("c")
465 .value_name("COLOR")
466 .takes_value(true),
467 )
468 .get_matches_from(args);
469 env::set_var("TERM", "xterm-256color");
470
471 let result = color(10, &matches);
472 assert_eq!(result, " 10".white().bold().to_string());
473 }
474
475 #[test]
476 fn color_none() {
477 let args = vec!["ds", "-c", "none"];
478 let matches = App::new("DiskSpace")
479 .arg(
480 Arg::with_name("color")
481 .short("c")
482 .value_name("COLOR")
483 .takes_value(true),
484 )
485 .get_matches_from(args);
486 env::set_var("TERM", "xterm-256color");
487
488 let result = color(10, &matches);
489 assert_eq!(result, " 10");
490 }
491
492 #[test]
493 fn settings_defaults() {
494 let args = vec!["ds"];
495 let matches = App::new("DiskSpace").get_matches_from(args);
496 let mut rs = ReportSettings::new();
497 rs.settings(&matches);
498 assert_eq!(rs.all, false);
499 assert_eq!(rs.reverse, false);
500 assert_eq!(rs.lines, 20);
501 }
502
503 #[test]
504 fn settings_all() {
505 let args = vec!["ds", "-a"];
506 let matches = App::new("DiskSpace")
507 .arg(Arg::with_name("all").short("a"))
508 .get_matches_from(args);
509 let mut rs = ReportSettings::new();
510 rs.settings(&matches);
511 assert_eq!(rs.all, true);
512 }
513
514 #[test]
515 fn settings_reverse() {
516 let args = vec!["ds", "-r"];
517 let matches = App::new("DiskSpace")
518 .arg(Arg::with_name("reverse").short("r"))
519 .get_matches_from(args);
520 let mut rs = ReportSettings::new();
521 rs.settings(&matches);
522 assert_eq!(rs.reverse, true);
523 }
524
525 #[test]
526 fn settings_lines() {
527 let args = vec!["ds", "-n", "10"];
528 let matches = App::new("DiskSpace")
529 .arg(Arg::with_name("lines").short("n").takes_value(true))
530 .get_matches_from(args);
531 let mut rs = ReportSettings::new();
532 rs.settings(&matches);
533 assert_eq!(rs.lines, 10);
534 }
535
536 #[test]
537 fn settings_lines_invalid_value() {
538 let args = vec!["ds", "-n", "apple"];
539 let matches = App::new("DiskSpace")
540 .arg(Arg::with_name("lines").short("n").takes_value(true))
541 .get_matches_from(args);
542 let mut rs = ReportSettings::new();
543 rs.settings(&matches);
544 assert_eq!(rs.lines, 20);
545 }
546
547 #[test]
548 fn settings_exclude() {
549 let args = vec!["ds", "-e", "apple", "pear"];
550 let matches = App::new("DiskSpace")
551 .arg(
552 Arg::with_name("exclude")
553 .short("e")
554 .min_values(1)
555 .multiple(true)
556 .takes_value(true),
557 )
558 .get_matches_from(args);
559 let mut rs = ReportSettings::new();
560 rs.settings(&matches);
561 assert_eq!(rs.exclude, vec!["apple".to_string(), "pear".to_string()]);
562 }
563
564}