Skip to main content

ferray_strings/
case.rs

1// ferray-strings: Case manipulation operations (REQ-5)
2//
3// Implements upper, lower, capitalize, title — elementwise on StringArray.
4
5use ferray_core::dimension::Dimension;
6use ferray_core::error::FerrayResult;
7
8use crate::string_array::StringArray;
9
10/// Convert each string element to uppercase.
11///
12/// # Errors
13/// Returns an error if the internal array construction fails.
14///
15/// # Examples
16/// ```ignore
17/// let a = strings::array(&["hello", "world"]).unwrap();
18/// let b = strings::upper(&a).unwrap();
19/// assert_eq!(b.as_slice(), &["HELLO", "WORLD"]);
20/// ```
21pub fn upper<D: Dimension>(a: &StringArray<D>) -> FerrayResult<StringArray<D>> {
22    a.map(|s| s.to_uppercase())
23}
24
25/// Convert each string element to lowercase.
26///
27/// # Errors
28/// Returns an error if the internal array construction fails.
29pub fn lower<D: Dimension>(a: &StringArray<D>) -> FerrayResult<StringArray<D>> {
30    a.map(|s| s.to_lowercase())
31}
32
33/// Capitalize each string element (first character uppercase, rest lowercase).
34///
35/// # Errors
36/// Returns an error if the internal array construction fails.
37pub fn capitalize<D: Dimension>(a: &StringArray<D>) -> FerrayResult<StringArray<D>> {
38    a.map(|s| {
39        let mut chars = s.chars();
40        match chars.next() {
41            None => String::new(),
42            Some(first) => {
43                let upper: String = first.to_uppercase().collect();
44                let rest: String = chars.as_str().to_lowercase();
45                format!("{upper}{rest}")
46            }
47        }
48    })
49}
50
51/// Title-case each string element (first letter of each word uppercase,
52/// rest lowercase).
53///
54/// Words are separated by whitespace.
55///
56/// # Errors
57/// Returns an error if the internal array construction fails.
58pub fn title<D: Dimension>(a: &StringArray<D>) -> FerrayResult<StringArray<D>> {
59    a.map(|s| {
60        let mut result = String::with_capacity(s.len());
61        let mut capitalize_next = true;
62        for ch in s.chars() {
63            if !ch.is_alphabetic() {
64                // Non-alphabetic characters pass through unchanged and
65                // mark the next alphabetic character as word-start (matching
66                // NumPy/Python str.title() behaviour).
67                result.push(ch);
68                capitalize_next = true;
69            } else if capitalize_next {
70                for upper in ch.to_uppercase() {
71                    result.push(upper);
72                }
73                capitalize_next = false;
74            } else {
75                for lower in ch.to_lowercase() {
76                    result.push(lower);
77                }
78            }
79        }
80        result
81    })
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use crate::string_array::array;
88
89    #[test]
90    fn test_upper() {
91        let a = array(&["hello", "world"]).unwrap();
92        let b = upper(&a).unwrap();
93        assert_eq!(b.as_slice(), &["HELLO", "WORLD"]);
94    }
95
96    #[test]
97    fn test_lower() {
98        let a = array(&["HELLO", "World"]).unwrap();
99        let b = lower(&a).unwrap();
100        assert_eq!(b.as_slice(), &["hello", "world"]);
101    }
102
103    #[test]
104    fn test_capitalize() {
105        let a = array(&["hello world", "fOO BAR", ""]).unwrap();
106        let b = capitalize(&a).unwrap();
107        assert_eq!(b.as_slice(), &["Hello world", "Foo bar", ""]);
108    }
109
110    #[test]
111    fn test_title() {
112        let a = array(&["hello world", "foo bar baz"]).unwrap();
113        let b = title(&a).unwrap();
114        assert_eq!(b.as_slice(), &["Hello World", "Foo Bar Baz"]);
115    }
116
117    #[test]
118    fn test_title_mixed_case() {
119        let a = array(&["hELLO wORLD"]).unwrap();
120        let b = title(&a).unwrap();
121        assert_eq!(b.as_slice(), &["Hello World"]);
122    }
123
124    #[test]
125    fn test_upper_empty() {
126        let a = array(&[""]).unwrap();
127        let b = upper(&a).unwrap();
128        assert_eq!(b.as_slice(), &[""]);
129    }
130}