use crate::{
ext::CharExt,
stream::{
ext::{CharSet, StrExt},
COMMA, CR, HT, LF, SP,
},
utf8char::FixedUtf8Char,
};
use std::marker::PhantomData;
pub trait Format<Char: CharExt = char>: Copy
where
for<'s> &'s str: StrExt<'s, Char>,
{
type Skip: CharSet<Item = Char>;
fn skip(self) -> Self::Skip;
}
impl<L: Format<Char>, Char: CharExt> Format<Char> for &L
where
for<'s> &'s str: StrExt<'s, Char>,
{
type Skip = L::Skip;
#[inline]
fn skip(self) -> Self::Skip {
L::skip(*self)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Default<Char>(PhantomData<Char>);
impl<Char> Default<Char> {
pub const fn new() -> Self {
Self(PhantomData)
}
}
const WHITE_SPACES: [FixedUtf8Char; 4] = [SP, HT, LF, CR];
impl Format<FixedUtf8Char> for Default<FixedUtf8Char> {
type Skip = &'static [FixedUtf8Char];
#[inline]
fn skip(self) -> Self::Skip {
&WHITE_SPACES
}
}
impl Format<char> for Default<char> {
type Skip = &'static [char];
#[inline]
fn skip(self) -> Self::Skip {
&[' ', '\t', '\n', '\r']
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CSV<Char>(PhantomData<Char>);
impl<Char> CSV<Char> {
pub const fn new() -> Self {
Self(PhantomData)
}
}
const CSV_SEP: [FixedUtf8Char; 5] = [SP, HT, COMMA, LF, CR];
impl Format<FixedUtf8Char> for CSV<FixedUtf8Char> {
type Skip = &'static [FixedUtf8Char];
#[inline]
fn skip(self) -> Self::Skip {
&CSV_SEP
}
}
impl Format<char> for CSV<char> {
type Skip = &'static [char];
#[inline]
fn skip(self) -> Self::Skip {
&[' ', '\t', ',', '\n', '\r']
}
}
#[derive(Debug, Clone, Default)]
pub struct Skip<Char = char>(Vec<Char>);
impl<Char> Skip<Char> {
pub const fn new() -> Self {
Self(Vec::new())
}
}
impl<Char: From<C> + Ord, C> FromIterator<C> for Skip<Char> {
#[inline]
fn from_iter<T: IntoIterator<Item = C>>(iter: T) -> Self {
let mut v: Vec<_> = iter.into_iter().map(From::from).collect();
v.sort();
v.dedup();
Self(v)
}
}
impl<'s> Format<FixedUtf8Char> for &'s Skip<FixedUtf8Char> {
type Skip = &'s [FixedUtf8Char];
#[inline]
fn skip(self) -> Self::Skip {
&self.0
}
}
impl<'s> Format<char> for &'s Skip<char> {
type Skip = &'s [char];
#[inline]
fn skip(self) -> Self::Skip {
&self.0
}
}
#[inline]
pub fn default<Char>() -> Default<Char> {
Default::new()
}
#[inline]
pub fn csv<Char>() -> CSV<Char> {
CSV::new()
}
#[inline]
pub fn skip<Char: Ord, T: IntoIterator<Item = Char>>(iter: T) -> Skip<Char> {
iter.into_iter().collect()
}
#[cfg(test)]
mod tests {
use super::Default;
use crate::{
ext::{Any, CharSet as _},
fmt::{Format, Skip, CSV, WHITE_SPACES},
utf8char::FixedUtf8Char,
};
fn equivalence_for_char(c: char) {
assert_eq!(
Default::<char>::new().skip().matches(c),
Default::<FixedUtf8Char>::new().skip().matches(c.into()),
);
assert_eq!(
Default::<char>::new().skip().matches(c),
Default::<FixedUtf8Char>::new().skip().matches(c.into()),
);
assert_eq!(
CSV::<char>::new().skip().matches(c),
CSV::<FixedUtf8Char>::new().skip().matches(c.into()),
);
assert_eq!(
CSV::<char>::new().skip().matches(c),
CSV::<FixedUtf8Char>::new().skip().matches(c.into()),
);
let seps = [' ', '\t', '\n', '\r'];
assert_eq!(
Skip::<char>::from_iter(seps).skip().matches(c),
Skip::<FixedUtf8Char>::from_iter(seps)
.skip()
.matches(c.into()),
);
assert_eq!(
Skip::<char>::from_iter(WHITE_SPACES).skip().matches(c),
Skip::<FixedUtf8Char>::from_iter(WHITE_SPACES)
.skip()
.matches(c.into()),
);
}
#[test]
fn equivalence_char() {
equivalence_for_char(' ');
equivalence_for_char('\t');
equivalence_for_char('\n');
equivalence_for_char('\r');
equivalence_for_char('a');
equivalence_for_char('å');
equivalence_for_char('🦀');
equivalence_for_char('中');
equivalence_for_char('文');
}
fn equivalence_for_string(s: &str) {
assert_eq!(
Default::<char>::new().skip().trim_end(s),
Default::<FixedUtf8Char>::new().skip().trim_end(s),
);
assert_eq!(
Default::<char>::new().skip().trim(s),
Default::<FixedUtf8Char>::new().skip().trim(s),
);
}
#[test]
fn equivalence_string() {
equivalence_for_string(" ");
equivalence_for_string("\t");
equivalence_for_string("\n");
equivalence_for_string("\r");
equivalence_for_string("a");
equivalence_for_string("å");
equivalence_for_string("🦀");
equivalence_for_string("中");
equivalence_for_string("文");
}
#[test]
fn any() {
for s in ["", " ", "\t", "\n", "\r", "a", "å", "🦀", "中", "文"].iter() {
let d = Any::<char>::new();
assert_eq!(d.trim_start(s), "");
assert_eq!(d.trim_end(s), "");
assert_eq!(d.trim(s), "");
}
}
}