1use regex::Regex;
2
3use super::HelpersError;
4
5pub trait StringExt {
6 fn is_empty_or_whitespaces(&self) -> bool;
7 fn split_get_first(&self, splitter: Option<String>) -> String;
8 fn split_get_last(&self, splitter: Option<String>) -> String;
9 fn get_first_char(&self) -> Option<char>;
10
11 fn trim_char(&self, trimmer: Option<String>) -> String;
12 fn trim_start_char(&self, trimmer: Option<String>) -> String;
13 fn trim_end_char(&self, trimmer: Option<String>) -> String;
14
15 fn uppercase_first_letter(&self) -> String;
16 fn lowercase_first_letter(&self) -> String;
17 fn pascal_case(&self) -> String;
18 fn camel_case(&self) -> String;
19 fn snake_case(&self) -> String;
20
21 fn on_one_line(&self, indent: Option<u64>, line_break: Option<bool>, replacer: Option<&str>) -> String;
22
23 fn regex_extract(&self, regex_extractor: &str, regex_replacer: Option<&str>, separator: Option<&str>) -> Result<String, HelpersError>;
24 fn regex_transform(&self, regex_pattern: &str, regex_replacer: &str) -> Result<String, HelpersError>;
25}
26
27impl StringExt for Option<String> {
30 fn is_empty_or_whitespaces(&self) -> bool {
31 self.as_ref().map_or(true, |s| s.is_empty_or_whitespaces())
32 }
33
34 fn split_get_first(&self, splitter: Option<String>) -> String {
35 self.as_ref().map_or(Default::default(), |s| s.split_get_first(splitter))
36 }
37
38 fn split_get_last(&self, splitter: Option<String>) -> String {
39 self.as_ref().map_or(Default::default(), |s| s.split_get_last(splitter))
40 }
41
42 fn get_first_char(&self) -> Option<char> {
43 self.as_ref().and_then(|s| s.get_first_char())
44 }
45
46 fn trim_char(&self, trimmer: Option<String>) -> String {
47 self.as_ref().map_or(Default::default(), |s| s.trim_char(trimmer))
48 }
49
50 fn trim_start_char(&self, trimmer: Option<String>) -> String {
51 self.as_ref().map_or(Default::default(), |s| s.trim_start_char(trimmer))
52 }
53
54 fn trim_end_char(&self, trimmer: Option<String>) -> String {
55 self.as_ref().map_or(Default::default(), |s| s.trim_end_char(trimmer))
56 }
57
58 fn uppercase_first_letter(&self) -> String {
59 self.as_ref().map_or(Default::default(), |s| s.uppercase_first_letter())
60 }
61
62 fn lowercase_first_letter(&self) -> String {
63 self.as_ref().map_or(Default::default(), |s| s.lowercase_first_letter())
64 }
65
66 fn pascal_case(&self) -> String {
67 self.as_ref().map_or(Default::default(), |s| s.pascal_case())
68 }
69
70 fn camel_case(&self) -> String {
71 self.as_ref().map_or(Default::default(), |s| s.camel_case())
72 }
73
74 fn snake_case(&self) -> String {
75 self.as_ref().map_or(Default::default(), |s| s.snake_case())
76 }
77
78 fn on_one_line(&self, indent: Option<u64>, line_break: Option<bool>, replacer: Option<&str>) -> String {
79 self
80 .as_ref()
81 .map_or(Default::default(), |s| s.on_one_line(indent, line_break, replacer))
82 }
83
84 fn regex_extract(&self, regex_extractor: &str, regex_replacer: Option<&str>, separator: Option<&str>) -> Result<String, HelpersError> {
85 self
86 .as_ref()
87 .map(|s| s.regex_extract(regex_extractor, regex_replacer, separator))
88 .transpose()
89 .map(|s| s.unwrap_or_default())
90 }
91 fn regex_transform(&self, regex_pattern: &str, regex_replacer: &str) -> Result<String, HelpersError> {
92 self
93 .as_ref()
94 .map(|s| s.regex_transform(regex_pattern, regex_replacer))
95 .transpose()
96 .map(|s| s.unwrap_or_default())
97 }
98}
99
100impl StringExt for String {
101 fn is_empty_or_whitespaces(&self) -> bool {
102 self.as_str().is_empty_or_whitespaces()
103 }
104
105 fn split_get_first(&self, splitter: Option<String>) -> String {
106 self.as_str().split_get_first(splitter)
107 }
108
109 fn split_get_last(&self, splitter: Option<String>) -> String {
110 self.as_str().split_get_last(splitter)
111 }
112
113 fn get_first_char(&self) -> Option<char> {
114 self.as_str().get_first_char()
115 }
116
117 fn trim_char(&self, trimmer: Option<String>) -> String {
118 self.as_str().trim_char(trimmer)
119 }
120
121 fn trim_start_char(&self, trimmer: Option<String>) -> String {
122 self.as_str().trim_start_char(trimmer)
123 }
124
125 fn trim_end_char(&self, trimmer: Option<String>) -> String {
126 self.as_str().trim_end_char(trimmer)
127 }
128
129 fn uppercase_first_letter(&self) -> String {
130 self.as_str().uppercase_first_letter()
131 }
132
133 fn lowercase_first_letter(&self) -> String {
134 self.as_str().lowercase_first_letter()
135 }
136
137 fn pascal_case(&self) -> String {
138 self.as_str().pascal_case()
139 }
140
141 fn camel_case(&self) -> String {
142 self.as_str().camel_case()
143 }
144
145 fn snake_case(&self) -> String {
146 self.as_str().snake_case()
147 }
148
149 fn on_one_line(&self, indent: Option<u64>, line_break: Option<bool>, replacer: Option<&str>) -> String {
150 self.as_str().on_one_line(indent, line_break, replacer)
151 }
152
153 fn regex_extract(&self, regex_extractor: &str, regex_replacer: Option<&str>, separator: Option<&str>) -> Result<String, HelpersError> {
154 self.as_str().regex_extract(regex_extractor, regex_replacer, separator)
155 }
156 fn regex_transform(&self, regex_pattern: &str, regex_replacer: &str) -> Result<String, HelpersError> {
157 self.as_str().regex_transform(regex_pattern, regex_replacer)
158 }
159}
160
161impl StringExt for &str {
162 fn is_empty_or_whitespaces(&self) -> bool {
163 self.is_empty() || self.trim().is_empty()
164 }
165
166 fn split_get_first(&self, splitter: Option<String>) -> String {
167 let c = splitter.get_first_char().unwrap_or('/');
168 self.split(c).find(|s| !s.is_empty_or_whitespaces()).unwrap_or_default().to_string()
169 }
170
171 fn split_get_last(&self, splitter: Option<String>) -> String {
172 let c = splitter.get_first_char().unwrap_or('/');
173 self
174 .split(c)
175 .filter(|s| !s.is_empty_or_whitespaces())
176 .last()
177 .unwrap_or_default()
178 .to_string()
179 }
180
181 fn get_first_char(&self) -> Option<char> {
182 self.chars().next()
183 }
184
185 fn trim_char(&self, trimmer: Option<String>) -> String {
186 let trimmer = trimmer.unwrap_or_else(|| " ".to_string()).chars().next().unwrap_or(' ');
187 self.trim_matches(trimmer).to_string()
188 }
189
190 fn trim_start_char(&self, trimmer: Option<String>) -> String {
191 let trimmer = trimmer.unwrap_or_else(|| " ".to_string()).chars().next().unwrap_or(' ');
192 self.trim_start_matches(trimmer).to_string()
193 }
194
195 fn trim_end_char(&self, trimmer: Option<String>) -> String {
196 let trimmer = trimmer.unwrap_or_else(|| " ".to_string()).chars().next().unwrap_or(' ');
197 self.trim_end_matches(trimmer).to_string()
198 }
199
200 fn uppercase_first_letter(&self) -> String {
201 if self.is_empty_or_whitespaces() {
202 return String::default();
203 }
204 let mut ve: Vec<char> = self.chars().collect();
205 ve[0] = ve[0].to_uppercase().next().unwrap();
206 let result: String = ve.into_iter().collect();
207 result
208 }
209
210 fn lowercase_first_letter(&self) -> String {
211 if self.is_empty_or_whitespaces() {
212 return String::default();
213 }
214 let mut ve: Vec<char> = self.chars().collect();
215 ve[0] = ve[0].to_lowercase().next().unwrap();
216 let result: String = ve.into_iter().collect();
217 result
218 }
219
220 fn pascal_case(&self) -> String {
221 let (_, pascal) = self.trim().chars().fold((Some(true), String::with_capacity(self.len())), |acc, x| {
222 let (upper_next, mut s) = acc;
223 if is_out_of_case(x) {
224 return (Some(true), s);
225 }
226 match upper_next {
227 Some(true) => s.push(x.to_uppercase().next().unwrap_or(x)),
228 _ => s.push(x),
229 }
230 (Some(false), s)
231 });
232 pascal
233 }
234
235 fn camel_case(&self) -> String {
236 let (_, camel) = self
237 .trim()
238 .chars()
239 .fold((Some(false), String::with_capacity(self.len())), |acc, x| {
240 let (upper_next, mut s) = acc;
241 if is_out_of_case(x) {
242 return (Some(true), s);
243 }
244
245 match upper_next {
246 Some(up) => {
247 let c = if up {
248 x.to_uppercase().next().unwrap_or(x)
249 } else {
250 x.to_lowercase().next().unwrap_or(x)
251 };
252 s.push(c);
253 }
254 None => s.push(x),
255 }
256 (None, s)
257 });
258 camel
259 }
260
261 fn snake_case(&self) -> String {
262 let (_, _, snake) = self
263 .trim()
264 .chars()
265 .fold((Some(true), Some(true), String::with_capacity(self.len())), |acc, x| {
266 let (previous_underscore, previous_upper, mut s) = acc;
267 if is_out_of_case(x) {
268 if !previous_underscore.unwrap_or(true) {
269 s.push('_');
270 }
271 return (Some(true), Some(false), s);
272 }
273
274 let is_upper = x.is_uppercase();
275 if is_upper && !previous_underscore.unwrap_or(false) && !previous_upper.unwrap_or(false) {
276 s.push('_');
277 }
278
279 s.push(x.to_lowercase().next().unwrap_or(x));
280
281 (Some(false), Some(is_upper), s)
282 });
283 snake
284 }
285
286 fn on_one_line(&self, indent: Option<u64>, line_break: Option<bool>, replacer: Option<&str>) -> String {
287 let replacer = replacer.unwrap_or("");
288 let r = ONE_LINER_REGEX.replace_all(self, replacer);
289
290 let eol = if line_break.unwrap_or(true) { "\n" } else { "" };
291 let indent = indent.unwrap_or_default();
292
293 let result = format!("{:indent$}{}{}", "", r.trim(), eol, indent = indent as usize);
294 result
295 }
296
297 fn regex_extract(&self, regex_extractor: &str, replacer: Option<&str>, separator: Option<&str>) -> Result<String, HelpersError> {
298 let regex_extr = Regex::new(regex_extractor)?;
299 let replacer = replacer.unwrap_or("$1");
300 let separator = separator.unwrap_or(", ");
301
302 let mut values = Vec::new();
303 for m in regex_extr.find_iter(self) {
304 let matched = m.as_str();
305 let replaced = regex_extr.replace(matched, replacer);
306 values.push(replaced.to_string());
307 }
308
309 Ok(values.join(separator))
310 }
311
312 fn regex_transform(&self, regex_pattern: &str, regex_replacer: &str) -> Result<String, HelpersError> {
313 let regex_extr = Regex::new(regex_pattern)?;
314 let transformed = regex_extr.replace_all(self, regex_replacer);
315 Ok(transformed.into())
316 }
317}
318
319static ONE_LINER_REGEX: once_cell::sync::Lazy<regex::Regex> =
320 once_cell::sync::Lazy::new(|| regex::Regex::new(r#" *[\r\n]+ *"#).expect("The ONE_LINER_REGEX regex did not compile."));
321
322fn is_out_of_case(c: char) -> bool {
323 !(c.is_alphabetic() || c.is_numeric())
324}
325
326#[cfg(test)]
327mod test {
328 use super::*;
329 use test_case::test_case;
330
331 #[test_case("/user/{username}", "\\{([^}]*)}", Some("$1"), None, "username")]
332 #[test_case("/user/{username}/{id}", "\\{([^}]*)}", Some("$1"), None, "username, id")]
333 #[test_case("/user/{username}/{id}", "\\{([^}]*)}", Some("<$1>"), Some("|"), "<username>|<id>")]
334 fn regex_extract_tests(arg: &str, regex_extractor: &str, regex_replacer: Option<&str>, separator: Option<&str>, expected: &str) {
335 let result = arg.regex_extract(regex_extractor, regex_replacer, separator).unwrap();
336 assert_eq!(result, expected);
337 }
338
339 #[test_case("/user/{username}", "\\{([^}]*)}", "$1", "/user/username")]
340 #[test_case("/user/{user}/{id}", "\\{([^}]*)}", "$1", "/user/user/id")]
341 #[test_case("/user/{username}/{id}", "\\{([^}]*)}", "<$1>", "/user/<username>/<id>")]
342 fn regex_transform_tests(arg: &str, regex_extractor: &str, regex_replacer: &str, expected: &str) {
343 let result = arg.regex_transform(regex_extractor, regex_replacer).unwrap();
344 assert_eq!(result, expected);
345 }
346
347 #[test_case(" ", true)]
348 #[test_case(" \t\n ", true)]
349 #[test_case(" \n", true)]
350 #[test_case("hello", false)]
351 fn is_empty_or_whitespaces_tests(v: &str, expected: bool) {
352 assert_eq!(v.is_empty_or_whitespaces(), expected);
353 assert_eq!(v.to_string().is_empty_or_whitespaces(), expected);
354 assert_eq!(Some(v.to_string()).is_empty_or_whitespaces(), expected);
355 }
356
357 #[test_case("leave/me/alone", "", "leave")]
358 #[test_case("/leave/me", "", "leave")]
359 #[test_case("/leave/me", "e", "/l")]
360 #[test_case("", "e", "")]
361 fn split_get_first_tests(v: &str, splitter: &str, expected: &str) {
362 let splitter = if splitter.is_empty() { None } else { Some(splitter.to_string()) };
363 assert_eq!(v.split_get_first(splitter.clone()), expected);
364 assert_eq!(v.to_string().split_get_first(splitter.clone()), expected);
365 assert_eq!(Some(v.to_string()).split_get_first(splitter), expected);
366 }
367
368 #[test_case("leave/me/alone", "", "alone")]
369 #[test_case("/leave/me/", "", "me")]
370 #[test_case("/leave/me", "e", "/m")]
371 #[test_case("", "e", "")]
372 fn split_get_last_tests(v: &str, splitter: &str, expected: &str) {
373 let splitter = if splitter.is_empty() { None } else { Some(splitter.to_string()) };
374 assert_eq!(v.split_get_last(splitter.clone()), expected);
375 assert_eq!(v.to_string().split_get_last(splitter.clone()), expected);
376 assert_eq!(Some(v.to_string()).split_get_last(splitter), expected);
377 }
378
379 #[test_case("", "e", "")]
380 #[test_case(" leave ", "", "leave")]
381 #[test_case("elle", "e", "ll")]
382 #[test_case("-test_", "-", "test_")]
383 #[test_case("leel", "e", "leel")]
384 fn del_char_tests(v: &str, trimmer: &str, expected: &str) {
386 let trimmer = if trimmer.is_empty_or_whitespaces() {
387 None
388 } else {
389 Some(trimmer.to_string())
390 };
391 assert_eq!(v.trim_char(trimmer), expected);
392 }
393
394 #[test_case("", "e", "")]
395 #[test_case(" leave ", "", "leave ")]
396 #[test_case("elle", "e", "lle")]
397 #[test_case("-test_", "_", "-test_")]
398 #[test_case("leel", "e", "leel")]
399 fn del_char_start_tests(v: &str, trimmer: &str, expected: &str) {
400 let trimmer = if trimmer.is_empty_or_whitespaces() {
401 None
402 } else {
403 Some(trimmer.to_string())
404 };
405 assert_eq!(v.trim_start_char(trimmer), expected);
406 }
407
408 #[test_case("", "e", "")]
409 #[test_case(" leave ", "", " leave")]
410 #[test_case("elle", "e", "ell")]
411 #[test_case("-test_", "-", "-test_")]
412 #[test_case("leel", "e", "leel")]
413 fn del_char_end_tests(v: &str, trimmer: &str, expected: &str) {
414 let trimmer = if trimmer.is_empty_or_whitespaces() {
415 None
416 } else {
417 Some(trimmer.to_string())
418 };
419 assert_eq!(v.trim_end_char(trimmer), expected);
420 }
421
422 #[test_case("42", "42")]
423 #[test_case("HELLO", "HELLO")]
424 #[test_case("HelloWorld", "HelloWorld")]
425 #[test_case("helloo", "Helloo")]
426 #[test_case("heLlo wOrld", "HeLloWOrld")]
427 #[test_case("hello_wwrld", "HelloWwrld")]
428 #[test_case("hello-worldd", "HelloWorldd")]
429 #[test_case("helo-WORLD", "HeloWORLD")]
430 #[test_case("hello/WOOLD", "HelloWOOLD")]
431 #[test_case("hello\\\\WORD", "HelloWORD")]
432 fn pascal_case_tests(v: &str, expected: &str) {
433 assert_eq!(v.pascal_case(), expected);
434 }
435
436 #[test_case("42", "42")]
437 #[test_case("HELLO", "hELLO")]
438 #[test_case("hey", "hey")]
439 #[test_case("heLlo wOrld", "heLloWOrld")]
440 #[test_case("hey_world", "heyWorld")]
441 #[test_case("helo-world", "heloWorld")]
442 #[test_case("helloo-WORLD", "hellooWORLD")]
443 #[test_case("HelooWorld", "helooWorld")]
444 fn camel_case_tests(v: &str, expected: &str) {
445 assert_eq!(v.camel_case(), expected);
446 }
447
448 #[test_case("42", "42")]
449 #[test_case("hello", "hello")]
450 #[test_case("helo world", "helo_world")]
451 #[test_case("helloo_world", "helloo_world")]
452 #[test_case("heloo-world", "heloo_world")]
453 #[test_case("hallo--world", "hallo_world")]
454 #[test_case("halo__World", "halo_world")]
455 #[test_case("haloo-World", "haloo_world")]
456 #[test_case("halloo _ world", "halloo_world")]
457 #[test_case("heello - world", "heello_world")]
458 #[test_case("HelloWoorld", "hello_woorld")]
459 #[test_case("heello _WOORLD", "heello_woorld")]
460 #[test_case(" HEELLO", "heello")]
461 #[test_case("Helo ", "helo")]
462 #[test_case("2Hello2 ", "2_hello2")]
463 #[test_case("HelloWorld_42LongName ", "hello_world_42_long_name")]
464 fn snake_case_tests(v: &str, expected: &str) {
465 assert_eq!(v.snake_case(), expected);
466 }
467
468 #[test_case("42", "42")]
469 #[test_case("hello", "hello")]
470 #[test_case("Test", "test")]
471 fn lowercase_first_letter_tests(v: &str, expected: &str) {
472 assert_eq!(v.lowercase_first_letter(), expected)
473 }
474
475 #[test_case("42", "42")]
476 #[test_case("HELLO", "HELLO")]
477 #[test_case("test", "Test")]
478 fn uppercase_first_letter_tests(v: &str, expected: &str) {
479 assert_eq!(v.uppercase_first_letter(), expected)
480 }
481}