harper-core 2.0.0

The language checker for developers.
Documentation
use std::borrow::Borrow;

use smallvec::SmallVec;

use crate::{CharString, char_string::CHAR_STRING_INLINE_SIZE};

/// Apply the casing of `template` to `target`.
///
/// If `template` is shorter than `target`, the casing of the last character of `template` will be reused for
/// the rest of the string.
///
/// If `template` is empty, all characters will be lowercased.
#[must_use]
pub fn copy_casing(
    template: impl IntoIterator<Item = impl Borrow<char>>,
    target: impl IntoIterator<Item = impl Borrow<char>>,
) -> CharString {
    target
        .into_iter()
        .scan(
            (template.into_iter().get_casing(), Case::Lower),
            |(template, prev_case), c| {
                // Skip non-alphabetic characters in `target` without advancing `template`.
                if c.borrow().is_alphabetic()
                    && let Some(template_case) = template.next()
                {
                    *prev_case = template_case;
                };
                Some(prev_case.apply_to(*c.borrow()))
            },
        )
        .flatten()
        .collect()
}

/// Represents the casing of a character.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Case {
    Upper,
    Lower,
}

impl Case {
    /// Apply the casing to a provided character.
    ///
    /// This essentially calls [`char::to_uppercase()`] or [`char::to_lowercase()`] depending on
    /// the state of `self`. Similarly to those functions, it returns an iterator of the resulting
    /// character(s).
    pub fn apply_to(&self, char: char) -> impl Iterator<Item = char> + use<> {
        match self {
            Self::Upper => char.to_uppercase().collect::<SmallVec<[char; 2]>>(),
            Self::Lower => char.to_lowercase().collect::<SmallVec<[char; 2]>>(),
        }
        .into_iter()
    }
}

impl TryFrom<char> for Case {
    type Error = ();

    /// Try to get the casing from the given character.
    ///
    /// This fails if the character is neither uppercase nor lowercase.
    fn try_from(value: char) -> Result<Self, Self::Error> {
        if value.is_uppercase() {
            Ok(Self::Upper)
        } else if value.is_lowercase() {
            Ok(Self::Lower)
        } else {
            Err(())
        }
    }
}

// TODO: maybe move this functionality to CharStringExt if and when CharStringExt can be
// generalized to work with char iterators.
pub trait CaseIterExt {
    fn get_casing(self) -> impl Iterator<Item = Case>;
    fn get_casing_unfiltered(self) -> SmallVec<[Option<Case>; CHAR_STRING_INLINE_SIZE]>;
}
impl<I: IntoIterator<Item = T>, T: Borrow<char>> CaseIterExt for I {
    /// Get an iterator of [`Case`] from a collection of characters. Note that this will not
    /// include cases for characters that are neither uppercase nor lowercase.
    fn get_casing(self) -> impl Iterator<Item = Case> {
        self.into_iter()
            .filter_map(|char| (*char.borrow()).try_into().ok())
    }

    /// Get casing for the provided string. Unlike [`Self::get_casing`], the output will always
    /// be the same length as the input string. If a character is neither uppercase nor lowercase,
    /// its corresponding case will be `None`.
    fn get_casing_unfiltered(self) -> SmallVec<[Option<Case>; CHAR_STRING_INLINE_SIZE]> {
        self.into_iter()
            .map(|c| Case::try_from(*c.borrow()).ok())
            .collect()
    }
}