1use crate::argument::Ascii;
4use core::fmt::Alignment;
5
6use crate::utils::{assert_is_ascii, count_chars};
7
8#[derive(Debug, Clone, Copy, PartialEq)]
10pub struct StrLength {
11 pub bytes: usize,
13 pub chars: usize,
15}
16
17impl StrLength {
18 pub(crate) const fn for_str(s: &str) -> Self {
19 Self {
20 bytes: s.len(),
21 chars: count_chars(s),
22 }
23 }
24
25 pub(crate) const fn for_char(c: char) -> Self {
26 Self {
27 bytes: c.len_utf8(),
28 chars: 1,
29 }
30 }
31
32 pub const fn both(value: usize) -> Self {
34 Self {
35 bytes: value,
36 chars: value,
37 }
38 }
39}
40
41#[derive(Debug, Clone, Copy)]
42pub(crate) struct Pad {
43 pub align: Alignment,
44 pub width: usize,
45 pub using: char,
46}
47
48impl Pad {
49 pub const fn compute_padding(&self, char_count: usize) -> (usize, usize) {
50 if char_count >= self.width {
51 return (0, 0);
52 }
53 match self.align {
54 Alignment::Left => (0, self.width - char_count),
55 Alignment::Right => (self.width - char_count, 0),
56 Alignment::Center => {
57 let total_padding = self.width - char_count;
58 (total_padding / 2, total_padding - total_padding / 2)
59 }
60 }
61 }
62}
63
64#[derive(Debug, Clone, Copy)]
132pub struct Fmt<T: FormatArgument> {
133 capacity: StrLength,
137 pub(crate) details: T::Details,
138 pub(crate) pad: Option<Pad>,
139}
140
141pub const fn fmt<T>() -> Fmt<T>
143where
144 T: FormatArgument<Details = ()> + MaxLength,
145{
146 Fmt {
147 capacity: T::MAX_LENGTH,
148 details: (),
149 pad: None,
150 }
151}
152
153pub const fn clip<'a>(clip_at: usize, using: &'static str) -> Fmt<&'a str> {
160 assert!(clip_at > 0, "Clip width must be positive");
161 Fmt {
162 capacity: StrLength {
163 bytes: clip_at * char::MAX_LENGTH.bytes + using.len(),
164 chars: clip_at + count_chars(using),
165 },
166 details: StrFormat { clip_at, using },
167 pad: None,
168 }
169}
170
171pub const fn clip_ascii<'a>(clip_at: usize, using: &'static str) -> Fmt<Ascii<'a>> {
177 assert!(clip_at > 0, "Clip width must be positive");
178 assert_is_ascii(using);
179 Fmt {
180 capacity: StrLength::both(clip_at + using.len()),
181 details: StrFormat { clip_at, using },
182 pad: None,
183 }
184}
185
186impl<T: FormatArgument> Fmt<T> {
187 const fn pad(mut self, align: Alignment, width: usize, using: char) -> Self {
188 let pad = Pad {
189 align,
190 width,
191 using,
192 };
193 self.pad = Some(pad);
194 self
195 }
196
197 #[must_use]
199 pub const fn pad_left(self, width: usize, using: char) -> Self {
200 self.pad(Alignment::Left, width, using)
201 }
202
203 #[must_use]
205 pub const fn pad_right(self, width: usize, using: char) -> Self {
206 self.pad(Alignment::Right, width, using)
207 }
208
209 #[must_use]
211 pub const fn pad_center(self, width: usize, using: char) -> Self {
212 self.pad(Alignment::Center, width, using)
213 }
214
215 #[doc(hidden)] pub const fn capacity(&self) -> usize {
218 if let Some(pad) = &self.pad {
219 let full_pad_capacity = pad.using.len_utf8() * pad.width;
221
222 let max_width = if self.capacity.chars > pad.width {
223 pad.width
224 } else {
225 self.capacity.chars
226 };
227 let min_pad_capacity =
229 pad.using.len_utf8() * (pad.width - max_width) + max_width * T::MAX_BYTES_PER_CHAR;
230
231 let pad_capacity = if full_pad_capacity > min_pad_capacity {
233 full_pad_capacity
234 } else {
235 min_pad_capacity
236 };
237 if pad_capacity > self.capacity.bytes {
238 return pad_capacity;
239 }
240 }
241 self.capacity.bytes
242 }
243}
244
245pub trait FormatArgument {
247 type Details: 'static + Copy;
249 #[doc(hidden)] const MAX_BYTES_PER_CHAR: usize;
252}
253
254impl FormatArgument for &str {
255 type Details = StrFormat;
256 const MAX_BYTES_PER_CHAR: usize = 4;
257}
258
259impl FormatArgument for Ascii<'_> {
260 type Details = StrFormat;
261 const MAX_BYTES_PER_CHAR: usize = 1;
262}
263
264#[doc(hidden)] #[derive(Debug, Clone, Copy)]
267pub struct StrFormat {
268 pub(crate) clip_at: usize,
269 pub(crate) using: &'static str,
270}
271
272pub trait MaxLength {
274 const MAX_LENGTH: StrLength;
276}
277
278macro_rules! impl_max_width_for_uint {
279 ($($uint:ty),+) => {
280 $(
281 impl MaxLength for $uint {
282 const MAX_LENGTH: StrLength = StrLength::both(
283 crate::ArgumentWrapper::new(Self::MAX).into_argument().formatted_len(),
284 );
285 }
286
287 impl FormatArgument for $uint {
288 type Details = ();
289 const MAX_BYTES_PER_CHAR: usize = 1;
290 }
291 )+
292 };
293}
294
295impl_max_width_for_uint!(u8, u16, u32, u64, u128, usize);
296
297macro_rules! impl_max_width_for_int {
298 ($($int:ty),+) => {
299 $(
300 impl MaxLength for $int {
301 const MAX_LENGTH: StrLength = StrLength::both(
302 crate::ArgumentWrapper::new(Self::MIN).into_argument().formatted_len(),
303 );
304 }
305
306 impl FormatArgument for $int {
307 type Details = ();
308 const MAX_BYTES_PER_CHAR: usize = 1;
309 }
310 )+
311 };
312}
313
314impl_max_width_for_int!(i8, i16, i32, i64, i128, isize);
315
316impl MaxLength for char {
317 const MAX_LENGTH: StrLength = StrLength { bytes: 4, chars: 1 };
318}
319
320impl FormatArgument for char {
321 type Details = ();
322 const MAX_BYTES_PER_CHAR: usize = 4;
323}
324
325#[cfg(test)]
326mod tests {
327 use std::string::ToString;
328
329 use super::*;
330
331 #[test]
332 fn max_length_bound_is_correct() {
333 assert_eq!(u8::MAX_LENGTH.bytes, u8::MAX.to_string().len());
334 assert_eq!(u16::MAX_LENGTH.bytes, u16::MAX.to_string().len());
335 assert_eq!(u32::MAX_LENGTH.bytes, u32::MAX.to_string().len());
336 assert_eq!(u64::MAX_LENGTH.bytes, u64::MAX.to_string().len());
337 assert_eq!(u128::MAX_LENGTH.bytes, u128::MAX.to_string().len());
338 assert_eq!(usize::MAX_LENGTH.bytes, usize::MAX.to_string().len());
339
340 assert_eq!(i8::MAX_LENGTH.bytes, i8::MIN.to_string().len());
341 assert_eq!(i16::MAX_LENGTH.bytes, i16::MIN.to_string().len());
342 assert_eq!(i32::MAX_LENGTH.bytes, i32::MIN.to_string().len());
343 assert_eq!(i64::MAX_LENGTH.bytes, i64::MIN.to_string().len());
344 assert_eq!(i128::MAX_LENGTH.bytes, i128::MIN.to_string().len());
345 assert_eq!(isize::MAX_LENGTH.bytes, isize::MIN.to_string().len());
346 }
347
348 #[test]
349 fn capacity_for_padded_format() {
350 let format = fmt::<u8>().pad(Alignment::Right, 8, ' ');
351 assert_eq!(format.capacity(), 8);
352 let format = fmt::<u8>().pad(Alignment::Right, 8, 'β');
353 assert_eq!(format.capacity(), 24); let format = fmt::<u64>().pad(Alignment::Right, 8, ' ');
355 assert_eq!(format.capacity(), u64::MAX.to_string().len()); let format = clip(8, "").pad(Alignment::Left, 8, ' ');
358 assert_eq!(format.capacity.chars, 8);
359 assert_eq!(format.capacity.bytes, 32);
360 assert_eq!(format.capacity(), 32);
361
362 let format = clip(4, "").pad(Alignment::Left, 8, ' ');
363 assert_eq!(format.capacity.chars, 4);
364 assert_eq!(format.capacity.bytes, 16);
365 assert_eq!(format.capacity(), 20); let format = clip(4, "").pad(Alignment::Left, 8, 'Γ');
368 assert_eq!(format.capacity.chars, 4);
369 assert_eq!(format.capacity.bytes, 16);
370 assert_eq!(format.capacity(), 24); let format = clip(4, "β¦").pad(Alignment::Left, 8, ' ');
373 assert_eq!(format.capacity.chars, 5);
374 assert_eq!(format.capacity.bytes, 16 + "β¦".len());
375 assert_eq!(format.capacity(), 23); }
377}