use core::convert::{TryFrom, TryInto};
use core::iter::FusedIterator;
use core::str::FromStr;
#[cfg(feature = "alloc")]
use alloc::borrow::Cow;
#[cfg(feature = "alloc")]
use alloc::collections::TryReserveError;
use crate::InvalidCaseMappingMode;
use crate::unicode::mapping::Mode;
mod ascii;
mod full;
use self::ascii::swapcase_byte;
pub use ascii::make_ascii_swapcase;
#[cfg(feature = "alloc")]
pub use ascii::to_ascii_swapcase;
#[derive(Debug, Clone)]
enum Inner<'a> {
Full(full::Swapcase<'a>),
Ascii(&'a [u8]),
}
#[derive(Debug, Clone)]
#[must_use = "Swapcase is an iterator and must be used"]
pub struct Swapcase<'a> {
iter: Inner<'a>,
}
impl Default for Swapcase<'_> {
fn default() -> Self {
Self::new()
}
}
impl<'a> Swapcase<'a> {
pub const fn new() -> Self {
Self::with_slice(b"")
}
pub const fn with_slice(slice: &'a [u8]) -> Self {
if slice.is_ascii() {
return Self::with_ascii_slice(slice);
}
Self {
iter: Inner::Full(full::Swapcase::with_slice(slice)),
}
}
pub const fn with_ascii_slice(slice: &'a [u8]) -> Self {
Self {
iter: Inner::Ascii(slice),
}
}
pub(crate) const fn with_mode(slice: &'a [u8], mode: Mode) -> Self {
if !mode.is_turkic() && slice.is_ascii() {
return Self::with_ascii_slice(slice);
}
Self {
iter: Inner::Full(full::Swapcase::with_mode(slice, mode)),
}
}
}
impl Iterator for Swapcase<'_> {
type Item = u8;
fn next(&mut self) -> Option<Self::Item> {
match &mut self.iter {
Inner::Full(iter) => iter.next(),
Inner::Ascii(slice) => {
let (&byte, remainder) = slice.split_first()?;
*slice = remainder;
Some(swapcase_byte(byte))
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match &self.iter {
Inner::Full(iter) => iter.size_hint(),
Inner::Ascii(slice) => (slice.len(), Some(slice.len())),
}
}
fn count(self) -> usize {
match self.iter {
Inner::Full(iter) => iter.count(),
Inner::Ascii(slice) => slice.len(),
}
}
}
impl FusedIterator for Swapcase<'_> {}
#[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum SwapcaseMode {
#[default]
Full,
Ascii,
Turkic,
Lithuanian,
}
impl TryFrom<&str> for SwapcaseMode {
type Error = InvalidCaseMappingMode;
#[inline]
fn try_from(value: &str) -> Result<Self, Self::Error> {
value.as_bytes().try_into()
}
}
impl TryFrom<Option<&str>> for SwapcaseMode {
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 SwapcaseMode {
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 SwapcaseMode {
type Error = InvalidCaseMappingMode;
#[inline]
fn try_from(value: Option<&[u8]>) -> Result<Self, Self::Error> {
match value {
None => Ok(Self::Full),
Some(b"ascii") => Ok(Self::Ascii),
Some(b"turkic") => Ok(Self::Turkic),
Some(b"lithuanian") => Ok(Self::Lithuanian),
Some(_) => Err(InvalidCaseMappingMode::new()),
}
}
}
impl FromStr for SwapcaseMode {
type Err = InvalidCaseMappingMode;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.try_into()
}
}
pub const fn swapcase(slice: &[u8], options: SwapcaseMode) -> Swapcase<'_> {
match options {
SwapcaseMode::Full | SwapcaseMode::Lithuanian => Swapcase::with_slice(slice),
SwapcaseMode::Ascii => Swapcase::with_ascii_slice(slice),
SwapcaseMode::Turkic => Swapcase::with_mode(slice, Mode::TurkicSwap),
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[inline]
pub fn try_to_swapcase(
slice: &[u8],
options: SwapcaseMode,
) -> Result<Cow<'_, [u8]>, TryReserveError> {
crate::collect::try_collect(slice, swapcase(slice, options))
}