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}