use std::collections::HashMap;
use std::fmt;
use yansi::Color::Blue;
use crate::format::HorizontalScale;
#[derive(Debug)]
pub struct CommonTerms {
pub terms: HashMap<String, usize>,
lines: usize,
}
impl CommonTerms {
pub fn new(lines: usize) -> Self {
Self {
terms: HashMap::new(),
lines,
}
}
pub fn observe(&mut self, term: String) {
*self.terms.entry(term).or_insert(0) += 1;
}
}
impl fmt::Display for CommonTerms {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let width = f.width().unwrap_or(100);
let mut counts: Vec<(&String, &usize)> = self.terms.iter().collect();
if counts.is_empty() {
writeln!(f, "No data")?;
return Ok(());
}
counts.sort_by(|a, b| b.1.cmp(a.1));
let values = &counts[..self.lines.min(counts.len())];
let label_width = values.iter().fold(1, |acc, x| acc.max(x.0.len()));
let horizontal_scale = HorizontalScale::new(counts[0].1 / width);
let width_count = format!("{}", counts[0].1).len();
writeln!(f, "{horizontal_scale}")?;
for (term, count) in values.iter() {
writeln!(
f,
"[{label}] [{count}] {bar}",
label = Blue.paint(format!("{term:>label_width$}")),
count = horizontal_scale.get_count(**count, width_count),
bar = horizontal_scale.get_bar(**count)
)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use yansi::Paint;
#[test]
fn test_common_terms_empty() {
let terms = CommonTerms::new(10);
Paint::disable();
let display = format!("{terms}");
assert_eq!(display, "No data\n");
}
#[test]
fn test_common_terms() {
let mut terms = CommonTerms::new(2);
for _ in 0..100 {
terms.observe(String::from("foo"));
}
for _ in 0..10 {
terms.observe(String::from("arrrrrrrr"));
}
for _ in 0..20 {
terms.observe(String::from("barbar"));
}
Paint::disable();
let display = format!("{terms:10}");
println!("{display}");
assert!(display.contains("[ foo] [100] ∎∎∎∎∎∎∎∎∎∎\n"));
assert!(display.contains("[barbar] [ 20] ∎∎\n"));
assert!(!display.contains("arr"));
}
}