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
use super::*;

const DEFAULT_BAR_VERTICAL: bool = false;
const DEFAULT_BAR_WIDTH_HORIZONTAL: usize = 5;
const DEFAULT_BAR_WIDTH_VERTICAL: usize = 1;
const DEFAULT_BAR_MAX_VAL: f64 = 100.0;

#[derive(Debug)]
pub struct BarFormatter {
    width: usize,
    max_value: f64,
    vertical: bool,
}

impl BarFormatter {
    pub(super) fn from_args(args: &[Arg]) -> Result<Self> {
        let mut vertical = DEFAULT_BAR_VERTICAL;
        let mut width = None;
        let mut max_value = DEFAULT_BAR_MAX_VAL;
        for arg in args {
            match arg.key {
                "width" | "w" => {
                    width = Some(arg.val.parse().error("Width must be a positive integer")?);
                }
                "max_value" => {
                    max_value = arg.val.parse().error("Max value must be a number")?;
                }
                "vertical" | "v" => {
                    vertical = arg.val.parse().error("Vertical value must be a bool")?;
                }
                other => {
                    return Err(Error::new(format!("Unknown argument for 'bar': '{other}'")));
                }
            }
        }
        Ok(Self {
            width: width.unwrap_or(match vertical {
                false => DEFAULT_BAR_WIDTH_HORIZONTAL,
                true => DEFAULT_BAR_WIDTH_VERTICAL,
            }),
            max_value,
            vertical,
        })
    }
}

const HORIZONTAL_BAR_CHARS: [char; 9] = [
    ' ', '\u{258f}', '\u{258e}', '\u{258d}', '\u{258c}', '\u{258b}', '\u{258a}', '\u{2589}',
    '\u{2588}',
];

const VERTICAL_BAR_CHARS: [char; 9] = [
    ' ', '\u{2581}', '\u{2582}', '\u{2583}', '\u{2584}', '\u{2585}', '\u{2586}', '\u{2587}',
    '\u{2588}',
];

impl Formatter for BarFormatter {
    fn format(&self, val: &Value, _config: &SharedConfig) -> Result<String, FormatError> {
        match val {
            Value::Number { mut val, .. } => {
                val = (val / self.max_value).clamp(0., 1.);
                if self.vertical {
                    let vert_char = VERTICAL_BAR_CHARS[(val * 8.) as usize];
                    Ok((0..self.width).map(|_| vert_char).collect())
                } else {
                    let chars_to_fill = val * self.width as f64;
                    Ok((0..self.width)
                        .map(|i| {
                            HORIZONTAL_BAR_CHARS
                                [((chars_to_fill - i as f64).clamp(0., 1.) * 8.) as usize]
                        })
                        .collect())
                }
            }
            other => Err(FormatError::IncompatibleFormatter {
                ty: other.type_name(),
                fmt: "bar",
            }),
        }
    }
}