1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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()
}
}