1#![allow(
32 clippy::missing_errors_doc,
33 clippy::missing_panics_doc,
34 clippy::many_single_char_names,
35 clippy::similar_names,
36 clippy::items_after_statements,
37 clippy::option_if_let_else,
38 clippy::too_long_first_doc_paragraph,
39 clippy::needless_pass_by_value,
40 clippy::match_same_arms
41)]
42
43pub mod align;
44pub mod case;
45pub mod classify;
46pub mod concat;
47pub mod regex_ops;
48pub mod search;
49pub mod split_join;
50pub mod str_ops;
51pub mod string_array;
52pub mod strip;
53
54pub use string_array::{StringArray, StringArray1, StringArray2, array};
56
57pub use align::{center, ljust, ljust_with, rjust, rjust_with, zfill};
59pub use case::{capitalize, lower, title, upper};
60pub use classify::{isalnum, isalpha, isdigit, islower, isnumeric, isspace, istitle, isupper};
61pub use concat::{add, add_same, multiply};
62pub use regex_ops::{extract, extract_compiled, match_, match_compiled};
63pub use regex::Regex;
66pub use search::{count, endswith, find, replace, startswith};
67pub use split_join::{join, join_array, split};
68pub use str_ops::{equal, greater, greater_equal, less, less_equal, not_equal, str_len, swapcase};
69pub use strip::{lstrip, rstrip, strip};
70
71#[cfg(test)]
72mod integration_tests {
73 use super::*;
74
75 #[test]
76 fn ac1_upper() {
77 let a = array(&["hello", "world"]).unwrap();
79 let b = upper(&a).unwrap();
80 assert_eq!(b.as_slice(), &["HELLO", "WORLD"]);
81 }
82
83 #[test]
84 fn ac2_add_broadcast_scalar() {
85 let a = array(&["hello", "world"]).unwrap();
87 let b = array(&["!"]).unwrap();
88 let c = add(&a, &b).unwrap();
89 assert_eq!(c.as_slice(), &["hello!", "world!"]);
90 }
91
92 #[test]
93 fn ac3_find_indices() {
94 let a = array(&["hello", "world"]).unwrap();
96 let b = find(&a, "ll").unwrap();
97 let data = b.as_slice().unwrap();
98 assert_eq!(data, &[2_i64, -1_i64]);
99 }
100
101 #[test]
102 fn ac4_split() {
103 let a = array(&["a-b", "c-d"]).unwrap();
105 let result = split(&a, "-").unwrap();
106 assert_eq!(
107 result,
108 vec![
109 vec!["a".to_string(), "b".to_string()],
110 vec!["c".to_string(), "d".to_string()],
111 ]
112 );
113 }
114
115 #[test]
116 fn ac5_regex() {
117 let a = array(&["abc123", "def", "ghi456"]).unwrap();
119
120 let matched = match_(&a, r"\d+").unwrap();
121 let matched_data = matched.as_slice().unwrap();
122 assert_eq!(matched_data, &[true, false, true]);
123
124 let extracted = extract(&a, r"(\d+)").unwrap();
125 assert_eq!(extracted.as_slice(), &["123", "", "456"]);
126 }
127
128 #[test]
129 fn full_pipeline() {
130 let raw = array(&[" Hello ", " World "]).unwrap();
132 let stripped = strip(&raw, None).unwrap();
133 let uppered = upper(&stripped).unwrap();
134 let suffix = array(&["!"]).unwrap();
135 let result = add(&uppered, &suffix).unwrap();
136 assert_eq!(result.as_slice(), &["HELLO!", "WORLD!"]);
137
138 let has_excl = endswith(&result, "!").unwrap();
139 let data = has_excl.as_slice().unwrap();
140 assert_eq!(data, &[true, true]);
141 }
142
143 #[test]
144 fn case_round_trip() {
145 let a = array(&["Hello World"]).unwrap();
146 let low = lower(&a).unwrap();
147 let titled = title(&low).unwrap();
148 assert_eq!(titled.as_slice(), &["Hello World"]);
149 }
150
151 #[test]
152 fn alignment_operations() {
153 let a = array(&["hi"]).unwrap();
154 let c = center(&a, 6, '-').unwrap();
155 assert_eq!(c.as_slice(), &["--hi--"]);
156
157 let l = ljust(&a, 6).unwrap();
158 assert_eq!(l.as_slice(), &["hi "]);
159
160 let r = rjust(&a, 6).unwrap();
161 assert_eq!(r.as_slice(), &[" hi"]);
162
163 let z = zfill(&array(&["42"]).unwrap(), 5).unwrap();
164 assert_eq!(z.as_slice(), &["00042"]);
165 }
166
167 #[test]
168 fn strip_operations() {
169 let a = array(&[" hello "]).unwrap();
170 assert_eq!(strip(&a, None).unwrap().as_slice(), &["hello"]);
171 assert_eq!(lstrip(&a, None).unwrap().as_slice(), &["hello "]);
172 assert_eq!(rstrip(&a, None).unwrap().as_slice(), &[" hello"]);
173 }
174
175 #[test]
176 fn search_operations() {
177 let a = array(&["hello world", "foo bar"]).unwrap();
178 let c = count(&a, "o").unwrap();
179 let data = c.as_slice().unwrap();
180 assert_eq!(data, &[2_u64, 2]);
182 }
183
184 #[test]
185 fn replace_operation() {
186 let a = array(&["hello world"]).unwrap();
187 let b = replace(&a, "world", "rust", None).unwrap();
188 assert_eq!(b.as_slice(), &["hello rust"]);
189 }
190
191 #[test]
192 fn multiply_operation() {
193 let a = array(&["ab"]).unwrap();
194 let b = multiply(&a, 3).unwrap();
195 assert_eq!(b.as_slice(), &["ababab"]);
196 }
197
198 #[test]
199 fn join_operation() {
200 let parts = vec![
201 vec!["a".to_string(), "b".to_string()],
202 vec!["c".to_string(), "d".to_string()],
203 ];
204 let result = join("-", &parts).unwrap();
205 assert_eq!(result.as_slice(), &["a-b", "c-d"]);
206 }
207
208 #[test]
209 fn capitalize_operation() {
210 let a = array(&["hello world", "RUST"]).unwrap();
211 let b = capitalize(&a).unwrap();
212 assert_eq!(b.as_slice(), &["Hello world", "Rust"]);
213 }
214
215 #[test]
216 fn string_array_2d() {
217 let a = StringArray2::from_rows(&[&["a", "b"], &["c", "d"]]).unwrap();
218 assert_eq!(a.shape(), &[2, 2]);
219 let b = upper(&a).unwrap();
220 assert_eq!(b.as_slice(), &["A", "B", "C", "D"]);
221 assert_eq!(b.shape(), &[2, 2]);
222 }
223
224 fn two_by_two(vals: &[&str; 4]) -> crate::StringArray2 {
234 crate::StringArray2::from_rows(&[&[vals[0], vals[1]], &[vals[2], vals[3]]]).unwrap()
235 }
236
237 #[test]
238 fn shape_preserved_case_ops_2d() {
239 let a = two_by_two(&["Hello", "World", "foo", "Bar"]);
240 assert_eq!(upper(&a).unwrap().shape(), &[2, 2]);
241 assert_eq!(lower(&a).unwrap().shape(), &[2, 2]);
242 assert_eq!(capitalize(&a).unwrap().shape(), &[2, 2]);
243 assert_eq!(title(&a).unwrap().shape(), &[2, 2]);
244 }
245
246 #[test]
247 fn shape_preserved_align_ops_2d() {
248 let a = two_by_two(&["a", "bb", "ccc", "dddd"]);
249 assert_eq!(center(&a, 6, ' ').unwrap().shape(), &[2, 2]);
250 assert_eq!(ljust(&a, 6).unwrap().shape(), &[2, 2]);
251 assert_eq!(rjust(&a, 6).unwrap().shape(), &[2, 2]);
252 assert_eq!(zfill(&a, 6).unwrap().shape(), &[2, 2]);
253 }
254
255 #[test]
256 fn shape_preserved_strip_ops_2d() {
257 let a = two_by_two(&[" a ", " b ", " c ", " d "]);
258 assert_eq!(strip(&a, None).unwrap().shape(), &[2, 2]);
259 assert_eq!(lstrip(&a, None).unwrap().shape(), &[2, 2]);
260 assert_eq!(rstrip(&a, None).unwrap().shape(), &[2, 2]);
261 }
262
263 #[test]
264 fn shape_preserved_concat_ops_2d() {
265 let a = two_by_two(&["ab", "cd", "ef", "gh"]);
266 let b = two_by_two(&["!", "!", "!", "!"]);
270 let ab = add(&a, &b).unwrap();
271 assert_eq!(ab.shape(), &[2, 2]);
272 assert_eq!(multiply(&a, 2).unwrap().shape(), &[2, 2]);
274 }
275
276 #[test]
277 fn shape_preserved_search_ops_2d() {
278 let a = two_by_two(&["hello", "help", "world", "word"]);
279 assert_eq!(find(&a, "ell").unwrap().shape(), &[2, 2]);
281 assert_eq!(count(&a, "l").unwrap().shape(), &[2, 2]);
282 assert_eq!(startswith(&a, "he").unwrap().shape(), &[2, 2]);
283 assert_eq!(endswith(&a, "d").unwrap().shape(), &[2, 2]);
284 assert_eq!(replace(&a, "l", "L", None).unwrap().shape(), &[2, 2]);
285 }
286
287 #[test]
288 fn shape_preserved_regex_ops_2d() {
289 let a = two_by_two(&["abc123", "x", "y42", "zzz"]);
290 assert_eq!(match_(&a, r"\d+").unwrap().shape(), &[2, 2]);
292 }
293
294 #[test]
295 fn shape_preserved_case_ops_3d() {
296 use ferray_core::dimension::Ix3;
298 let data: Vec<String> = (0..8).map(|i| format!("s{i}")).collect();
299 let a = crate::StringArray::<Ix3>::from_vec(Ix3::new([2, 2, 2]), data).unwrap();
300 assert_eq!(upper(&a).unwrap().shape(), &[2, 2, 2]);
301 assert_eq!(lower(&a).unwrap().shape(), &[2, 2, 2]);
302 }
303
304 #[test]
307 fn unicode_upper_lower() {
308 let a = array(&["café", "naïve", "über"]).unwrap();
309 let u = upper(&a).unwrap();
310 assert_eq!(u.as_slice(), &["CAFÉ", "NAÏVE", "ÜBER"]);
311 let l = lower(&u).unwrap();
312 assert_eq!(l.as_slice(), &["café", "naïve", "über"]);
313 }
314
315 #[test]
316 fn unicode_capitalize() {
317 let a = array(&["ñoño", "straße"]).unwrap();
318 let c = capitalize(&a).unwrap();
319 assert_eq!(c.as_slice()[0], "Ñoño");
320 assert_eq!(c.as_slice()[1], "Straße");
322 }
323
324 #[test]
325 fn unicode_find() {
326 let a = array(&["日本語テスト", "こんにちは"]).unwrap();
327 let r = find(&a, "テスト").unwrap();
328 let data = r.as_slice().unwrap();
329 assert_eq!(data[0], 3); assert!(data[0] >= 0); assert_eq!(data[1], -1); }
337
338 #[test]
339 fn unicode_strip() {
340 let a = array(&[" héllo ", " wörld "]).unwrap();
341 let s = strip(&a, None).unwrap();
342 assert_eq!(s.as_slice(), &["héllo", "wörld"]);
343 }
344
345 #[test]
346 fn unicode_replace() {
347 let a = array(&["café latte"]).unwrap();
348 let r = replace(&a, "café", "tea", None).unwrap();
349 assert_eq!(r.as_slice(), &["tea latte"]);
350 }
351
352 #[test]
353 fn emoji_operations() {
354 let a = array(&["hello 🌍", "rust 🦀"]).unwrap();
355 let u = upper(&a).unwrap();
356 assert_eq!(u.as_slice(), &["HELLO 🌍", "RUST 🦀"]);
357 let c = count(&a, "🌍").unwrap();
358 assert_eq!(c.as_slice().unwrap(), &[1, 0]);
359 }
360
361 #[test]
362 fn cjk_characters() {
363 let a = array(&["你好世界", "こんにちは"]).unwrap();
364 let starts = startswith(&a, "你好").unwrap();
365 assert_eq!(starts.as_slice().unwrap(), &[true, false]);
366 let ends = endswith(&a, "世界").unwrap();
367 assert_eq!(ends.as_slice().unwrap(), &[true, false]);
368 }
369
370 #[test]
373 fn empty_array_upper() {
374 let a = StringArray1::from_vec(ferray_core::dimension::Ix1::new([0]), vec![]).unwrap();
375 let u = upper(&a).unwrap();
376 assert_eq!(u.len(), 0);
377 }
378
379 #[test]
380 fn empty_array_str_len() {
381 let a = StringArray1::from_vec(ferray_core::dimension::Ix1::new([0]), vec![]).unwrap();
382 let l = str_len(&a).unwrap();
383 assert_eq!(l.size(), 0);
384 }
385
386 #[test]
387 fn empty_array_find() {
388 let a = StringArray1::from_vec(ferray_core::dimension::Ix1::new([0]), vec![]).unwrap();
389 let f = find(&a, "x").unwrap();
390 assert_eq!(f.size(), 0);
391 }
392}