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_whitespace() {
64                result.push(ch);
65                capitalize_next = true;
66            } else if capitalize_next {
67                for upper in ch.to_uppercase() {
68                    result.push(upper);
69                }
70                capitalize_next = false;
71            } else {
72                for lower in ch.to_lowercase() {
73                    result.push(lower);
74                }
75            }
76        }
77        result
78    })
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use crate::string_array::array;
85
86    #[test]
87    fn test_upper() {
88        let a = array(&["hello", "world"]).unwrap();
89        let b = upper(&a).unwrap();
90        assert_eq!(b.as_slice(), &["HELLO", "WORLD"]);
91    }
92
93    #[test]
94    fn test_lower() {
95        let a = array(&["HELLO", "World"]).unwrap();
96        let b = lower(&a).unwrap();
97        assert_eq!(b.as_slice(), &["hello", "world"]);
98    }
99
100    #[test]
101    fn test_capitalize() {
102        let a = array(&["hello world", "fOO BAR", ""]).unwrap();
103        let b = capitalize(&a).unwrap();
104        assert_eq!(b.as_slice(), &["Hello world", "Foo bar", ""]);
105    }
106
107    #[test]
108    fn test_title() {
109        let a = array(&["hello world", "foo bar baz"]).unwrap();
110        let b = title(&a).unwrap();
111        assert_eq!(b.as_slice(), &["Hello World", "Foo Bar Baz"]);
112    }
113
114    #[test]
115    fn test_title_mixed_case() {
116        let a = array(&["hELLO wORLD"]).unwrap();
117        let b = title(&a).unwrap();
118        assert_eq!(b.as_slice(), &["Hello World"]);
119    }
120
121    #[test]
122    fn test_upper_empty() {
123        let a = array(&[""]).unwrap();
124        let b = upper(&a).unwrap();
125        assert_eq!(b.as_slice(), &[""]);
126    }
127}