use core::convert::{TryFrom, TryInto};
use core::iter::FusedIterator;
use core::str::FromStr;
use crate::InvalidCaseMappingMode;
use crate::unicode::mapping::Mode;
mod ascii;
mod full;
mod georgian;
pub use ascii::make_ascii_capitalize;
#[cfg(feature = "alloc")]
pub use ascii::to_ascii_capitalize;
#[derive(Debug, Clone)]
#[allow(variant_size_differences)]
enum Inner<'a> {
Empty,
Full(full::Capitalize<'a>),
Ascii(ascii::Capitalize<'a>),
}
#[derive(Debug, Clone)]
#[must_use = "Capitalize is an Iterator and must be used"]
pub struct Capitalize<'a> {
iter: Inner<'a>,
}
impl Default for Capitalize<'_> {
fn default() -> Self {
Self::new()
}
}
impl<'a> Capitalize<'a> {
pub const fn new() -> Self {
Self { iter: Inner::Empty }
}
pub const fn with_slice(slice: &'a [u8]) -> Self {
Self {
iter: Inner::Full(full::Capitalize::with_slice(slice)),
}
}
pub const fn with_ascii_slice(slice: &'a [u8]) -> Self {
Self {
iter: Inner::Ascii(ascii::Capitalize::with_slice(slice)),
}
}
pub(crate) const fn with_mode(slice: &'a [u8], mode: Mode) -> Self {
Self {
iter: Inner::Full(full::Capitalize::with_mode(slice, mode)),
}
}
}
impl Iterator for Capitalize<'_> {
type Item = u8;
fn next(&mut self) -> Option<Self::Item> {
match self.iter {
Inner::Empty => None,
Inner::Full(ref mut iter) => iter.next(),
Inner::Ascii(ref mut iter) => iter.next(),
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self.iter {
Inner::Empty => (0, Some(0)),
Inner::Full(ref iter) => iter.size_hint(),
Inner::Ascii(ref iter) => iter.size_hint(),
}
}
fn count(self) -> usize {
match self.iter {
Inner::Empty => 0,
Inner::Full(iter) => iter.count(),
Inner::Ascii(iter) => iter.count(),
}
}
}
impl FusedIterator for Capitalize<'_> {}
#[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum CapitalizeMode {
#[default]
Full,
Ascii,
Turkic,
Lithuanian,
}
impl TryFrom<&str> for CapitalizeMode {
type Error = InvalidCaseMappingMode;
#[inline]
fn try_from(value: &str) -> Result<Self, Self::Error> {
value.as_bytes().try_into()
}
}
impl TryFrom<Option<&str>> for CapitalizeMode {
type Error = InvalidCaseMappingMode;
#[inline]
fn try_from(value: Option<&str>) -> Result<Self, Self::Error> {
value.map(str::as_bytes).try_into()
}
}
impl TryFrom<&[u8]> for CapitalizeMode {
type Error = InvalidCaseMappingMode;
#[inline]
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
match value {
b"ascii" => Ok(Self::Ascii),
b"turkic" => Ok(Self::Turkic),
b"lithuanian" => Ok(Self::Lithuanian),
_ => Err(InvalidCaseMappingMode::new()),
}
}
}
impl TryFrom<Option<&[u8]>> for CapitalizeMode {
type Error = InvalidCaseMappingMode;
#[inline]
fn try_from(value: Option<&[u8]>) -> Result<Self, Self::Error> {
match value {
None => Ok(Self::default()),
Some(value) => value.try_into(),
}
}
}
impl FromStr for CapitalizeMode {
type Err = InvalidCaseMappingMode;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.try_into()
}
}
pub const fn capitalize(slice: &[u8], options: CapitalizeMode) -> Capitalize<'_> {
match options {
CapitalizeMode::Full | CapitalizeMode::Lithuanian => Capitalize::with_slice(slice),
CapitalizeMode::Ascii => Capitalize::with_ascii_slice(slice),
CapitalizeMode::Turkic => Capitalize::with_mode(slice, Mode::TurkicTitle),
}
}
#[cfg(test)]
mod tests {
use core::{convert::TryInto, str::FromStr};
use super::CapitalizeMode;
use crate::InvalidCaseMappingMode;
use alloc::vec::Vec;
use bstr::ByteSlice;
use super::Capitalize;
#[test]
fn empty() {
let iter = Capitalize::new();
assert_eq!(iter.collect::<Vec<_>>().as_bstr(), b"".as_bstr());
let iter = Capitalize::default();
assert_eq!(iter.collect::<Vec<_>>().as_bstr(), b"".as_bstr());
let iter = Capitalize::with_slice(b"");
assert_eq!(iter.collect::<Vec<_>>().as_bstr(), b"".as_bstr());
let iter = Capitalize::with_ascii_slice(b"");
assert_eq!(iter.collect::<Vec<_>>().as_bstr(), b"".as_bstr());
}
#[test]
fn size_hint() {
assert_eq!(Capitalize::new().size_hint(), (0, Some(0)));
assert_eq!(Capitalize::default().size_hint(), (0, Some(0)));
assert_eq!(Capitalize::with_slice(b"").size_hint(), (0, Some(0)));
assert_eq!(
Capitalize::with_slice(b"abc, xyz").size_hint(),
(8, Some(8))
);
assert_eq!(
Capitalize::with_slice(b"abc, \xFF\xFE, xyz").size_hint(),
(3, Some(144))
);
assert_eq!(
Capitalize::with_slice("�".as_bytes()).size_hint(),
(1, Some(36))
);
assert_eq!(
Capitalize::with_slice("Έτος".as_bytes()).size_hint(),
(2, Some(96))
);
assert_eq!(
Capitalize::with_slice("ZȺȾ".as_bytes()).size_hint(),
(2, Some(60))
);
let mut utf8_with_invalid_bytes = b"\xFF\xFE".to_vec();
utf8_with_invalid_bytes.extend_from_slice("Έτος".as_bytes());
assert_eq!(
Capitalize::with_slice(&utf8_with_invalid_bytes).size_hint(),
(3, Some(120))
);
assert_eq!(
Capitalize::with_ascii_slice(b"abc, xyz").size_hint(),
(8, Some(8))
);
assert_eq!(
Capitalize::with_ascii_slice(b"abc, \xFF\xFE, xyz").size_hint(),
(12, Some(12))
);
assert_eq!(
Capitalize::with_ascii_slice("�".as_bytes()).size_hint(),
(3, Some(3))
);
assert_eq!(
Capitalize::with_ascii_slice("Έτος".as_bytes()).size_hint(),
(8, Some(8))
);
assert_eq!(
Capitalize::with_ascii_slice("ZȺȾ".as_bytes()).size_hint(),
(5, Some(5))
);
let mut utf8_with_invalid_bytes = b"\xFF\xFE".to_vec();
utf8_with_invalid_bytes.extend_from_slice("Έτος".as_bytes());
assert_eq!(
Capitalize::with_ascii_slice(&utf8_with_invalid_bytes).size_hint(),
(10, Some(10))
);
}
#[test]
fn count() {
assert_eq!(Capitalize::new().count(), 0);
assert_eq!(Capitalize::default().count(), 0);
assert_eq!(Capitalize::with_slice(b"").count(), 0);
assert_eq!(Capitalize::with_slice(b"abc, xyz").count(), 8);
assert_eq!(Capitalize::with_slice(b"abc, \xFF\xFE, xyz").count(), 12);
assert_eq!(Capitalize::with_slice("�".as_bytes()).count(), 3);
assert_eq!(Capitalize::with_slice("Έτος".as_bytes()).count(), 8);
assert_eq!(Capitalize::with_slice("ZȺȾ".as_bytes()).count(), 7);
let mut utf8_with_invalid_bytes = b"\xFF\xFE".to_vec();
utf8_with_invalid_bytes.extend_from_slice("Έτος".as_bytes());
assert_eq!(Capitalize::with_slice(&utf8_with_invalid_bytes).count(), 10);
assert_eq!(Capitalize::with_ascii_slice(b"abc, xyz").count(), 8);
assert_eq!(
Capitalize::with_ascii_slice(b"abc, \xFF\xFE, xyz").count(),
12
);
assert_eq!(Capitalize::with_ascii_slice("�".as_bytes()).count(), 3);
assert_eq!(Capitalize::with_ascii_slice("Έτος".as_bytes()).count(), 8);
assert_eq!(Capitalize::with_ascii_slice("ZȺȾ".as_bytes()).count(), 5);
let mut utf8_with_invalid_bytes = b"\xFF\xFE".to_vec();
utf8_with_invalid_bytes.extend_from_slice("Έτος".as_bytes());
assert_eq!(
Capitalize::with_ascii_slice(&utf8_with_invalid_bytes).count(),
10
);
}
#[test]
fn size_hint_covers_count() {
let iter = Capitalize::new();
let (min, max) = iter.size_hint();
let count = iter.count();
assert!(min <= count);
assert!(count <= max.unwrap());
}
#[test]
fn test_capitalize_mode_parsing() {
assert_eq!(CapitalizeMode::from_str("ascii"), Ok(CapitalizeMode::Ascii));
assert_eq!(
CapitalizeMode::from_str("turkic"),
Ok(CapitalizeMode::Turkic)
);
assert_eq!(
CapitalizeMode::from_str("lithuanian"),
Ok(CapitalizeMode::Lithuanian)
);
assert_eq!(
CapitalizeMode::from_str("full"),
Err(InvalidCaseMappingMode::new())
);
}
#[test]
fn test_capitalize_mode_conversion() {
let mut mode: CapitalizeMode;
mode = "turkic".try_into().unwrap();
assert_eq!(mode, CapitalizeMode::Turkic);
mode = Some("turkic").try_into().unwrap();
assert_eq!(mode, CapitalizeMode::Turkic);
mode = b"turkic"[..].try_into().unwrap();
assert_eq!(mode, CapitalizeMode::Turkic);
mode = Some(&b"turkic"[..]).try_into().unwrap();
assert_eq!(mode, CapitalizeMode::Turkic);
}
}