use crate::argument::Ascii;
use core::fmt::Alignment;
use crate::utils::{assert_is_ascii, count_chars};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct StrLength {
pub bytes: usize,
pub chars: usize,
}
impl StrLength {
pub(crate) const fn for_str(s: &str) -> Self {
Self {
bytes: s.len(),
chars: count_chars(s),
}
}
pub(crate) const fn for_char(c: char) -> Self {
Self {
bytes: c.len_utf8(),
chars: 1,
}
}
pub const fn both(value: usize) -> Self {
Self {
bytes: value,
chars: value,
}
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Pad {
pub align: Alignment,
pub width: usize,
pub using: char,
}
impl Pad {
pub const fn compute_padding(&self, char_count: usize) -> (usize, usize) {
if char_count >= self.width {
return (0, 0);
}
match self.align {
Alignment::Left => (0, self.width - char_count),
Alignment::Right => (self.width - char_count, 0),
Alignment::Center => {
let total_padding = self.width - char_count;
(total_padding / 2, total_padding - total_padding / 2)
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Fmt<T: FormatArgument> {
capacity: StrLength,
pub(crate) details: T::Details,
pub(crate) pad: Option<Pad>,
}
pub const fn fmt<T>() -> Fmt<T>
where
T: FormatArgument<Details = ()> + MaxLength,
{
Fmt {
capacity: T::MAX_LENGTH,
details: (),
pad: None,
}
}
pub const fn clip<'a>(clip_at: usize, using: &'static str) -> Fmt<&'a str> {
assert!(clip_at > 0, "Clip width must be positive");
Fmt {
capacity: StrLength {
bytes: clip_at * char::MAX_LENGTH.bytes + using.len(),
chars: clip_at + count_chars(using),
},
details: StrFormat { clip_at, using },
pad: None,
}
}
pub const fn clip_ascii<'a>(clip_at: usize, using: &'static str) -> Fmt<Ascii<'a>> {
assert!(clip_at > 0, "Clip width must be positive");
assert_is_ascii(using);
Fmt {
capacity: StrLength::both(clip_at + using.len()),
details: StrFormat { clip_at, using },
pad: None,
}
}
impl<T: FormatArgument> Fmt<T> {
const fn pad(mut self, align: Alignment, width: usize, using: char) -> Self {
let pad = Pad {
align,
width,
using,
};
self.pad = Some(pad);
self
}
#[must_use]
pub const fn pad_left(self, width: usize, using: char) -> Self {
self.pad(Alignment::Left, width, using)
}
#[must_use]
pub const fn pad_right(self, width: usize, using: char) -> Self {
self.pad(Alignment::Right, width, using)
}
#[must_use]
pub const fn pad_center(self, width: usize, using: char) -> Self {
self.pad(Alignment::Center, width, using)
}
#[doc(hidden)] pub const fn capacity(&self) -> usize {
if let Some(pad) = &self.pad {
let full_pad_capacity = pad.using.len_utf8() * pad.width;
let max_width = if self.capacity.chars > pad.width {
pad.width
} else {
self.capacity.chars
};
let min_pad_capacity =
pad.using.len_utf8() * (pad.width - max_width) + max_width * T::MAX_BYTES_PER_CHAR;
let pad_capacity = if full_pad_capacity > min_pad_capacity {
full_pad_capacity
} else {
min_pad_capacity
};
if pad_capacity > self.capacity.bytes {
return pad_capacity;
}
}
self.capacity.bytes
}
}
pub trait FormatArgument {
type Details: 'static + Copy;
#[doc(hidden)] const MAX_BYTES_PER_CHAR: usize;
}
impl FormatArgument for &str {
type Details = StrFormat;
const MAX_BYTES_PER_CHAR: usize = 4;
}
impl FormatArgument for Ascii<'_> {
type Details = StrFormat;
const MAX_BYTES_PER_CHAR: usize = 1;
}
#[doc(hidden)] #[derive(Debug, Clone, Copy)]
pub struct StrFormat {
pub(crate) clip_at: usize,
pub(crate) using: &'static str,
}
pub trait MaxLength {
const MAX_LENGTH: StrLength;
}
macro_rules! impl_max_width_for_uint {
($($uint:ty),+) => {
$(
impl MaxLength for $uint {
const MAX_LENGTH: StrLength = StrLength::both(
crate::ArgumentWrapper::new(Self::MAX).into_argument().formatted_len(),
);
}
impl FormatArgument for $uint {
type Details = ();
const MAX_BYTES_PER_CHAR: usize = 1;
}
)+
};
}
impl_max_width_for_uint!(u8, u16, u32, u64, u128, usize);
macro_rules! impl_max_width_for_int {
($($int:ty),+) => {
$(
impl MaxLength for $int {
const MAX_LENGTH: StrLength = StrLength::both(
crate::ArgumentWrapper::new(Self::MIN).into_argument().formatted_len(),
);
}
impl FormatArgument for $int {
type Details = ();
const MAX_BYTES_PER_CHAR: usize = 1;
}
)+
};
}
impl_max_width_for_int!(i8, i16, i32, i64, i128, isize);
impl MaxLength for char {
const MAX_LENGTH: StrLength = StrLength { bytes: 4, chars: 1 };
}
impl FormatArgument for char {
type Details = ();
const MAX_BYTES_PER_CHAR: usize = 4;
}
#[cfg(test)]
mod tests {
use std::string::ToString;
use super::*;
#[test]
fn max_length_bound_is_correct() {
assert_eq!(u8::MAX_LENGTH.bytes, u8::MAX.to_string().len());
assert_eq!(u16::MAX_LENGTH.bytes, u16::MAX.to_string().len());
assert_eq!(u32::MAX_LENGTH.bytes, u32::MAX.to_string().len());
assert_eq!(u64::MAX_LENGTH.bytes, u64::MAX.to_string().len());
assert_eq!(u128::MAX_LENGTH.bytes, u128::MAX.to_string().len());
assert_eq!(usize::MAX_LENGTH.bytes, usize::MAX.to_string().len());
assert_eq!(i8::MAX_LENGTH.bytes, i8::MIN.to_string().len());
assert_eq!(i16::MAX_LENGTH.bytes, i16::MIN.to_string().len());
assert_eq!(i32::MAX_LENGTH.bytes, i32::MIN.to_string().len());
assert_eq!(i64::MAX_LENGTH.bytes, i64::MIN.to_string().len());
assert_eq!(i128::MAX_LENGTH.bytes, i128::MIN.to_string().len());
assert_eq!(isize::MAX_LENGTH.bytes, isize::MIN.to_string().len());
}
#[test]
fn capacity_for_padded_format() {
let format = fmt::<u8>().pad(Alignment::Right, 8, ' ');
assert_eq!(format.capacity(), 8);
let format = fmt::<u8>().pad(Alignment::Right, 8, 'β');
assert_eq!(format.capacity(), 24); let format = fmt::<u64>().pad(Alignment::Right, 8, ' ');
assert_eq!(format.capacity(), u64::MAX.to_string().len());
let format = clip(8, "").pad(Alignment::Left, 8, ' ');
assert_eq!(format.capacity.chars, 8);
assert_eq!(format.capacity.bytes, 32);
assert_eq!(format.capacity(), 32);
let format = clip(4, "").pad(Alignment::Left, 8, ' ');
assert_eq!(format.capacity.chars, 4);
assert_eq!(format.capacity.bytes, 16);
assert_eq!(format.capacity(), 20);
let format = clip(4, "").pad(Alignment::Left, 8, 'Γ');
assert_eq!(format.capacity.chars, 4);
assert_eq!(format.capacity.bytes, 16);
assert_eq!(format.capacity(), 24);
let format = clip(4, "β¦").pad(Alignment::Left, 8, ' ');
assert_eq!(format.capacity.chars, 5);
assert_eq!(format.capacity.bytes, 16 + "β¦".len());
assert_eq!(format.capacity(), 23); }
}