Skip to main content

pito_list/
bar.rs

1use std::fmt::{self, Write};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4#[non_exhaustive]
5pub struct Bar<'a> {
6    pub(crate) track: &'a str,
7    pub(crate) thumb: &'a str,
8    pub(crate) halves: Option<(&'a str, &'a str)>,
9}
10
11impl<'a> Bar<'a> {
12    pub const LINE: Bar<'static> = Bar::new("│", "┃").halves("╽", "╿");
13
14    pub const fn new(track: &'a str, thumb: &'a str) -> Self {
15        Bar {
16            track,
17            thumb,
18            halves: None,
19        }
20    }
21
22    pub const fn halves(mut self, top: &'a str, bottom: &'a str) -> Self {
23        self.halves = Some((top, bottom));
24        self
25    }
26
27    pub(crate) fn glyph(&self, upper: bool, lower: bool) -> Option<&'a str> {
28        match (upper, lower, self.halves) {
29            (true, true, _) => Some(self.thumb),
30            (false, false, _) => None,
31            (false, true, Some((top, _))) => Some(top),
32            (true, false, Some((_, bottom))) => Some(bottom),
33            (_, _, None) => Some(self.thumb),
34        }
35    }
36}
37
38impl Default for Bar<'_> {
39    fn default() -> Self {
40        Bar::LINE
41    }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
45#[non_exhaustive]
46pub enum Place {
47    #[default]
48    Line,
49    Foot,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53#[non_exhaustive]
54pub struct Count<'a> {
55    pub(crate) to: &'a str,
56    pub(crate) of: &'a str,
57    pub(crate) group: &'a str,
58    pub(crate) place: Place,
59}
60
61impl<'a> Count<'a> {
62    pub const fn new(to: &'a str, of: &'a str) -> Self {
63        Count {
64            to,
65            of,
66            group: "",
67            place: Place::Line,
68        }
69    }
70
71    pub const fn group(mut self, separator: &'a str) -> Self {
72        self.group = separator;
73        self
74    }
75
76    pub const fn place(mut self, place: Place) -> Self {
77        self.place = place;
78        self
79    }
80
81    pub fn label(&self, first: usize, last: usize, total: usize) -> impl fmt::Display + use<'a> {
82        Label {
83            count: *self,
84            first,
85            last,
86            total,
87        }
88    }
89}
90
91struct Label<'a> {
92    count: Count<'a>,
93    first: usize,
94    last: usize,
95    total: usize,
96}
97
98impl Label<'_> {
99    fn number(&self, f: &mut fmt::Formatter<'_>, value: usize) -> fmt::Result {
100        let mut digits = [0u8; 20];
101        let mut at = digits.len();
102        let mut rest = value;
103        loop {
104            at -= 1;
105            digits[at] = b'0' + (rest % 10) as u8;
106            rest /= 10;
107            if rest == 0 {
108                break;
109            }
110        }
111        let digits = &digits[at..];
112        let lead = match digits.len() % 3 {
113            0 => 3,
114            lead => lead,
115        };
116        for (n, &digit) in digits.iter().enumerate() {
117            if n >= lead && (n - lead).is_multiple_of(3) {
118                f.write_str(self.count.group)?;
119            }
120            f.write_char(char::from(digit))?;
121        }
122        Ok(())
123    }
124}
125
126impl fmt::Display for Label<'_> {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        self.number(f, self.first)?;
129        if self.last != self.first {
130            f.write_str(self.count.to)?;
131            self.number(f, self.last)?;
132        }
133        f.write_str(self.count.of)?;
134        self.number(f, self.total)
135    }
136}
137
138pub(crate) struct Stack {
139    bytes: [u8; 128],
140    len: usize,
141}
142
143impl Stack {
144    pub(crate) const fn new() -> Self {
145        Stack {
146            bytes: [0; 128],
147            len: 0,
148        }
149    }
150
151    pub(crate) fn text(&self) -> &str {
152        std::str::from_utf8(&self.bytes[..self.len]).unwrap_or_default()
153    }
154}
155
156impl Write for Stack {
157    fn write_str(&mut self, text: &str) -> fmt::Result {
158        for c in text.chars() {
159            let end = self.len + c.len_utf8();
160            if end > self.bytes.len() {
161                return Err(fmt::Error);
162            }
163            c.encode_utf8(&mut self.bytes[self.len..end]);
164            self.len = end;
165        }
166        Ok(())
167    }
168}
169
170fn share(value: usize, times: usize, over: usize) -> usize {
171    if over == 0 {
172        return 0;
173    }
174    let exact = (value as u128 * times as u128 * 2 + over as u128) / (over as u128 * 2);
175    usize::try_from(exact).unwrap_or(usize::MAX)
176}
177
178pub(crate) fn thumb(top: usize, lines: usize, height: usize, unit: usize) -> (usize, usize) {
179    let track = height.saturating_mul(unit);
180    if lines <= height || track == 0 {
181        return (0, track);
182    }
183    let most = track.saturating_sub(1).max(unit).min(track);
184    let size = share(track, height, lines).clamp(unit.min(track), most);
185    let room = track - size;
186    let end = lines - height;
187    let top = top.min(end);
188    let mut start = share(top, room, end);
189    if room >= 2 && top > 0 && top < end {
190        start = start.clamp(1, room - 1);
191    }
192    (start, size)
193}