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