diff --git a/src/output.rs b/src/output.rs index 22fb3ef..fddbab8 100644 --- a/src/output.rs +++ b/src/output.rs @@ -39,26 +39,36 @@ pub fn color_me(arg: &dyn Display, color: Color, effect: Effect) -> String { #[allow(unused)] pub fn title(arg: &dyn Display) -> String { - color_me(arg, Color::BrightGreen, Effect::Underline) + return color_me(arg, Color::BrightGreen, Effect::Underline); } #[allow(unused)] pub fn info(arg: &dyn Display) -> String { - color_me(arg, Color::BrightBlue, Effect::Default) + return color_me(arg, Color::BrightBlue, Effect::Default); } #[allow(unused)] pub fn warn(arg: &dyn Display) -> String { - color_me(arg, Color::Yellow, Effect::Default) + return color_me(arg, Color::Yellow, Effect::Default); } #[allow(unused)] pub fn err(arg: &dyn Display) -> String { - color_me(arg, Color::Red, Effect::Default) + return color_me(arg, Color::Red, Effect::Default); } -fn spaces(width: usize) -> String { - return String::from_utf8(vec![32_u8; width]).unwrap(); +#[allow(unused)] +pub fn strong(arg: &dyn Display) -> String { + return color_me(arg, Color::Default, Effect::Bold); +} + +pub fn fill_char(chr: char, width: usize) -> String { + let s = vec![chr as u16; width]; + return String::from_utf16(&s).unwrap(); +} + +pub fn spaces(width: usize) -> String { + return fill_char(' ', width); } pub fn display_width(s: &String) -> usize { @@ -69,7 +79,7 @@ pub fn display_width(s: &String) -> usize { } #[allow(unused)] -pub fn align_center(s: &dyn ToString, width: usize) -> String { +pub fn center_justify(s: &dyn ToString, width: usize) -> String { let mut string = s.to_string(); let d_width = display_width(&string); if d_width < width { @@ -87,7 +97,7 @@ pub fn align_center(s: &dyn ToString, width: usize) -> String { } #[allow(unused)] -pub fn align_left(s: &dyn ToString, width: usize) -> String { +pub fn left_justify(s: &dyn ToString, width: usize) -> String { let mut string = s.to_string(); let d_width = display_width(&string); if d_width < width { @@ -98,7 +108,7 @@ pub fn align_left(s: &dyn ToString, width: usize) -> String { } #[allow(unused)] -pub fn align_right(s: &dyn ToString, width: usize) -> String { +pub fn right_justify(s: &dyn ToString, width: usize) -> String { let mut string = s.to_string(); let d_width = display_width(&string); if d_width < width { @@ -124,17 +134,17 @@ fn test_color() { #[test] fn test_align() { let s = "HelloWorld"; - assert_eq!(align_center(&s, 14), String::from(" HelloWorld ")); - assert_eq!(align_center(&s, 15), String::from(" HelloWorld ")); - assert_eq!(align_left(&s, 15), String::from("HelloWorld ")); - assert_eq!(align_right(&s, 15), String::from(" HelloWorld")); + assert_eq!(center_justify(&s, 14), String::from(" HelloWorld ")); + assert_eq!(center_justify(&s, 15), String::from(" HelloWorld ")); + assert_eq!(left_justify(&s, 15), String::from("HelloWorld ")); + assert_eq!(right_justify(&s, 15), String::from(" HelloWorld")); let t = title( &vec![ - align_left(&"Name", 8), - align_right(&"Files", 5), - align_right(&"Dirs", 5), - align_right(&"Size", 9), + left_justify(&"Name", 8), + right_justify(&"Files", 5), + right_justify(&"Dirs", 5), + right_justify(&"Size", 9), ] .join(" "), ); @@ -153,10 +163,10 @@ fn test_non_ascii() { let s3 = "Séamile: 🌊😀"; let s4 = "EVA,人の作り出した物"; - let aligned_s1 = align_right(&s1, 25); - let aligned_s2 = align_center(&s2, 25); - let aligned_s3 = align_left(&s3, 25); - let aligned_s4 = align_left(&s4, 25); + let aligned_s1 = right_justify(&s1, 25); + let aligned_s2 = center_justify(&s2, 25); + let aligned_s3 = left_justify(&s3, 25); + let aligned_s4 = left_justify(&s4, 25); println!("s1 => |{}|", aligned_s1); println!("s2 => |{}|", aligned_s2); diff --git a/src/walker.rs b/src/walker.rs index 7dac95d..09daa03 100644 --- a/src/walker.rs +++ b/src/walker.rs @@ -35,6 +35,7 @@ pub struct Counter { impl Counter { const SZ_UNIT: [&str; 7] = ["B", "K", "M", "G", "T", "P", "E"]; + /// Create a new Counter pub fn new(dirpath: &PathBuf, with_size: bool) -> Self { return Self { dirpath: dirpath.clone(), @@ -49,16 +50,19 @@ impl Counter { }; } - fn name(&self) -> &str { + // the dirpath with `&str` type + fn path(&self) -> &str { return self.dirpath.to_str().expect("dir path err"); } + // get the file size from Metadata fn file_size(metadata: &fs::Metadata) -> u64 { let sz = metadata.st_size() as f64; let blksz = metadata.st_blksize() as f64; return (blksz * (sz / blksz).ceil()) as u64; } + // calculate the total size of files in dirpath pub fn size(&self) -> u64 { match self.sz_map.as_ref() { Some(mp) => mp.values().sum(), @@ -66,9 +70,8 @@ impl Counter { } } - // Make "size" more readable - fn readable_size(&self) -> String { - let mut sz = self.size() as f64; + fn add_unit_to_size(size: u64) -> String { + let mut sz = size as f64; let mut str_sz = String::new(); for unit in Self::SZ_UNIT { @@ -86,6 +89,11 @@ impl Counter { return str_sz; } + // make "size" more readable + fn readable_size(&self) -> String { + return Self::add_unit_to_size(self.size()); + } + // merge from anther Counter fn merge(&mut self, other: &Self) { if other.dirpath.starts_with(&self.dirpath) { @@ -97,52 +105,105 @@ impl Counter { } } + // get the length of each field for display fn lengths(&self) -> Lengths { return ( - op::display_width(&self.name().to_string()), + op::display_width(&self.path().to_string()), self.n_files.to_string().len(), self.n_dirs.to_string().len(), self.readable_size().len(), ); } - fn make_title(with_size: bool, lens: Lengths) -> String { - let f0 = op::align_left(&"Name", lens.0); - let f1 = op::align_right(&"Files", lens.1); - let f2 = op::align_right(&"Dirs", lens.2); + fn join_fields(fields: Vec<&dyn ToString>, with_size: bool, lens: Lengths) -> String { + let f0 = op::left_justify(fields[0], lens.0); + let f1 = op::right_justify(fields[1], lens.1); + let f2 = op::right_justify(fields[2], lens.2); if with_size { - let f3 = op::align_right(&"Size", lens.3); - return op::title(&vec![f0, f1, f2, f3].join(" ")); + let f3 = op::right_justify(fields[3], lens.3); + return vec![f0, f1, f2, f3].join(" "); } else { - return op::title(&vec![f0, f1, f2].join(" ")); + return vec![f0, f1, f2].join(" "); } } - fn join_fields(&self, lens: Lengths) -> String { - let f0 = op::align_left(&self.name(), lens.0); - let f1 = op::align_right(&self.n_files, lens.1); - let f2 = op::align_right(&self.n_dirs, lens.2); - if self.sz_map == None { - return vec![f0, f1, f2].join(" "); - } else { - let f3 = op::align_right(&self.readable_size(), lens.3); - return vec![f0, f1, f2, f3].join(" "); + fn to_string(&self, lens: Lengths) -> String { + let path = self.path(); + let size = self.readable_size(); + let fields: Vec<&dyn ToString> = vec![&path, &self.n_files, &self.n_dirs, &size]; + let with_size = self.sz_map != None; + return Self::join_fields(fields, with_size, lens); + } + + fn make_head_line(with_size: bool, lens: Lengths) -> String { + let fields: Vec<&dyn ToString> = vec![&"Path", &"Files", &"Dirs", &"Size"]; + return op::title(&Self::join_fields(fields, with_size, lens)); + } + + fn make_total_line( + total: (String, String, String, String), + with_size: bool, + lens: Lengths, + ) -> String { + let fields: Vec<&dyn ToString> = vec![&total.0, &total.1, &total.2, &total.3]; + let total_line = Self::join_fields(fields, with_size, lens); + let hor_line = op::fill_char('─', total_line.len()); + + return format!("{}\n{}", hor_line, op::strong(&total_line)); + } + + fn summarize(counters: &Vec) -> (String, String, String, String) { + let mut sum = (0_u64, 0_u64, 0_u64); + for c in counters { + sum.0 += c.n_files; + sum.1 += c.n_dirs; + sum.2 += c.size(); + } + + return ( + String::from("Total"), + sum.0.to_string(), + sum.1.to_string(), + Self::add_unit_to_size(sum.2), + ); + } + + fn max_lengths(lens: Vec) -> Lengths { + let mut max_lens: Lengths = (0, 0, 0, 0); + for (l0, l1, l2, l3) in lens { + max_lens.0 = max_lens.0.max(l0); + max_lens.1 = max_lens.1.max(l1); + max_lens.2 = max_lens.2.max(l2); + max_lens.3 = max_lens.3.max(l3); } + return max_lens; } pub fn output(counters: &Vec, with_size: bool) { + let total = if counters.len() > 1 { + Self::summarize(counters) + } else { + (String::new(), String::new(), String::new(), String::new()) + }; + + // calculate the max value from `title`, `total` and `contents` lengths + let title_lens = (4_usize, 5_usize, 4_usize, 4_usize); + let total_lens = (total.0.len(), total.1.len(), total.2.len(), total.3.len()); + let mut lens = Vec::from_iter(counters.iter().map(|c| c.lengths())); + lens.append(&mut vec![total_lens, title_lens]); + let max_lens = Self::max_lengths(lens); + + // create the output lines from title, content and total let mut lines: Vec = vec![]; - let lens = counters - .iter() - .map(|c| c.lengths()) - .map(|w| (w.0.max(4), w.1.max(5), w.2.max(4), w.3.max(4))) - .reduce(|m, n| (n.0.max(m.0), n.1.max(m.1), n.2.max(m.2), n.3.max(m.3))) - .unwrap(); - - // make title and content lines - lines.push(op::title(&Self::make_title(with_size, lens))); + lines.push(Self::make_head_line(with_size, max_lens)); for cnt in counters { - lines.push(cnt.join_fields(lens)); + lines.push(cnt.to_string(max_lens)); + } + + // output the total only when there is more than one counters + if counters.len() > 1 { + let total_line = Self::make_total_line(total, with_size, max_lens); + lines.push(total_line); } // output