1use lazy_static::lazy_static;
4use regex::Regex;
5
6pub const HASH_PRIME: u64 = 0x100000001B3;
8pub const HASH_BASIS: u64 = 0xCBF29CE484222325;
9
10pub fn hash(s: &str) -> u64 {
22 let mut result = HASH_BASIS;
23 for &byte in s.as_bytes() {
24 result ^= byte as u64;
25 result = result.wrapping_mul(HASH_PRIME);
26 }
27 result
28}
29pub const fn hash_const(s: &str) -> u64 {
42 let bytes = s.as_bytes();
43 let mut result = HASH_BASIS;
44 let mut i = 0;
45 while i < bytes.len() {
46 result ^= bytes[i] as u64;
47 result = result.wrapping_mul(HASH_PRIME);
48 i += 1;
49 }
50 result
51}
52
53pub const fn hash_compile_time(s: &str) -> u64 {
55 hash_const(s)
56}
57
58pub fn replace_all_distinct(s: &str, from: &str, to: &str) -> String {
70 let mut result = s.to_string();
71 let mut position = 0;
72
73 while let Some(found_pos) = result[position..].find(from) {
74 let absolute_pos = position + found_pos;
75 result.replace_range(absolute_pos..absolute_pos + from.len(), to);
76 position = absolute_pos + to.len();
77 }
78
79 result
80}
81
82pub fn starts_with(s: &str, prefix: &str) -> bool {
93 s.starts_with(prefix)
94}
95
96pub fn ends_with(s: &str, suffix: &str) -> bool {
107 s.ends_with(suffix)
108}
109
110pub fn to_lower(s: &str) -> String {
120 s.to_lowercase()
121}
122
123pub fn trim(s: &str) -> &str {
133 s.trim()
134}
135
136pub fn trim_whitespace(s: &str, before: bool, after: bool) -> String {
137 if before {
138 s.trim_start().to_string()
139 } else if after {
140 s.trim_end().to_string()
141 } else {
142 s.trim().to_string()
143 }
144}
145
146pub fn trim_of(s: &str, target: char, before: bool, after: bool) -> String {
159 if !before && !after {
160 return s.to_string();
161 }
162
163 let len = s.len();
164 if len == 0 {
165 return s.to_string();
166 }
167
168 let mut start = 0;
169 let mut end = len;
170
171 if before {
172 for (i, ch) in s.char_indices() {
173 if ch != target {
174 start = i;
175 break;
176 }
177 }
178 }
179
180 if after {
181 for (i, ch) in s.char_indices().rev() {
182 if ch != target {
183 end = i + ch.len_utf8();
184 break;
185 }
186 }
187 }
188
189 if start >= end {
191 return String::new();
192 }
193
194 s[start..end].to_string()
195}
196
197pub fn find_str(s: &str, search: &str) -> Option<usize> {
208 s.find(search)
209}
210
211pub fn join<T: AsRef<str>>(parts: &[T], separator: &str) -> String {
222 parts
223 .iter()
224 .map(|s| s.as_ref())
225 .collect::<Vec<&str>>()
226 .join(separator)
227}
228
229lazy_static! {
230 static ref EMOJI_REGEX: Regex = Regex::new(r"\p{Emoji_Presentation}|\p{Extended_Pictographic}").unwrap();
233}
234
235pub fn remove_emoji(s: &str) -> String {
249 EMOJI_REGEX.replace_all(s, "").into_owned()
251}
252
253pub fn md5(input: &str) -> String {
263 use md5::{Digest, Md5};
264
265 let mut hasher = Md5::new();
266 hasher.update(input.as_bytes());
267 let result = hasher.finalize();
268
269 let mut hex_string = String::with_capacity(32);
271 for byte in result.iter() {
272 hex_string.push_str(&format!("{:02x}", byte));
273 }
274
275 hex_string
276}
277
278pub fn join_path(base: &str, segment: &str) -> String {
281 if base.is_empty() {
282 return segment.to_string();
283 }
284
285 let base_has_trailing_slash = base.ends_with('/');
286 let segment_has_leading_slash = segment.starts_with('/');
287
288 match (base_has_trailing_slash, segment_has_leading_slash) {
289 (true, true) => format!("{}{}", base, &segment[1..]),
290 (false, false) => format!("{}/{}", base, segment),
291 (true, false) => format!("{}{}", base, segment),
292 (false, true) => format!("{}{}", base, segment),
293 }
294}
295
296pub fn normalize_dir_path(path: &str) -> String {
298 if path.is_empty() {
299 return String::new();
300 }
301
302 if path.ends_with('/') {
303 path.to_string()
304 } else {
305 format!("{}/", path)
306 }
307}
308
309pub fn build_dir_entry_path(base_path: &str, dir_name: &str) -> String {
311 let base = normalize_dir_path(base_path);
312
313 if base.is_empty() {
314 format!("/{}/", dir_name)
315 } else {
316 join_path(&base, &format!("{}/", dir_name))
317 }
318}
319
320pub fn build_file_entry_path(base_path: &str, file_name: &str) -> String {
322 if base_path.is_empty() {
323 format!("/{}", file_name)
324 } else {
325 join_path(base_path, file_name)
326 }
327}
328
329pub fn normalize_file_path(path: &str) -> String {
331 if path.is_empty() {
332 return String::new();
333 }
334
335 if path.starts_with('/') {
336 path.to_string()
337 } else {
338 format!("/{}", path)
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345
346 #[test]
347 fn test_hash() {
348 assert_eq!(hash("test"), 18007334074686647077);
350 assert_eq!(hash("hello"), 11831194018420276491);
351 assert_eq!(hash(""), HASH_BASIS);
352 }
353
354 #[test]
355 fn test_hash_const() {
356 assert_eq!(hash_const("test"), hash("test"));
358 assert_eq!(hash_const("hello"), hash("hello"));
359 assert_eq!(hash_const(""), HASH_BASIS);
360 }
361
362 #[test]
363 fn test_replace_all_distinct() {
364 assert_eq!(replace_all_distinct("hello world", "o", "0"), "hell0 w0rld");
365 assert_eq!(replace_all_distinct("test-test", "-", "_"), "test_test");
366 assert_eq!(replace_all_distinct("abcabc", "a", "x"), "xbcxbc");
367 }
368
369 #[test]
370 fn test_starts_with() {
371 assert!(starts_with("hello world", "hello"));
372 assert!(!starts_with("hello world", "world"));
373 }
374
375 #[test]
376 fn test_ends_with() {
377 assert!(ends_with("hello world", "world"));
378 assert!(!ends_with("hello world", "hello"));
379 }
380
381 #[test]
382 fn test_to_lower() {
383 assert_eq!(to_lower("HELLO"), "hello");
384 assert_eq!(to_lower("Hello World"), "hello world");
385 }
386
387 #[test]
388 fn test_trim() {
389 assert_eq!(trim(" hello "), "hello");
390 assert_eq!(trim("\t\nhello\r\n"), "hello");
391 }
392
393 #[test]
394 fn test_join() {
395 let parts = vec!["a", "b", "c"];
396 assert_eq!(join(&parts, ","), "a,b,c");
397 assert_eq!(join(&parts, ""), "abc");
398 assert_eq!(join(&parts, " - "), "a - b - c");
399
400 let empty: Vec<&str> = vec![];
402 assert_eq!(join(&empty, ","), "");
403 }
404
405 #[test]
406 fn test_remove_emoji() {
407 assert_eq!(remove_emoji("๐Hello"), "Hello");
409 assert_eq!(remove_emoji("๐๐Hello"), "Hello");
411 assert_eq!(remove_emoji("Hello"), "Hello");
413 assert_eq!(remove_emoji("๐"), "๐"); }
416
417 #[test]
418 fn test_md5() {
419 assert_eq!(md5(""), "d41d8cd98f00b204e9800998ecf8427e");
421 assert_eq!(md5("hello world"), "5eb63bbbe01eeed093cb22bb8f5acdc3");
422 assert_eq!(md5("test"), "098f6bcd4621d373cade4e832627b4f6");
423 }
424
425 #[test]
426 fn test_join_path() {
427 assert_eq!(join_path("", "file.txt"), "file.txt");
428 assert_eq!(join_path("/", "file.txt"), "/file.txt");
429 assert_eq!(join_path("dir", "file.txt"), "dir/file.txt");
430 assert_eq!(join_path("dir/", "file.txt"), "dir/file.txt");
431 assert_eq!(join_path("dir", "/file.txt"), "dir/file.txt");
432 assert_eq!(join_path("dir/", "/file.txt"), "dir/file.txt");
433 assert_eq!(join_path("/dir", "subdir/file.txt"), "/dir/subdir/file.txt");
434 }
435
436 #[test]
437 fn test_normalize_dir_path() {
438 assert_eq!(normalize_dir_path(""), "");
439 assert_eq!(normalize_dir_path("/"), "/");
440 assert_eq!(normalize_dir_path("dir"), "dir/");
441 assert_eq!(normalize_dir_path("dir/"), "dir/");
442 assert_eq!(normalize_dir_path("/dir"), "/dir/");
443 assert_eq!(normalize_dir_path("/dir/"), "/dir/");
444 }
445
446 #[test]
447 fn test_build_dir_entry_path() {
448 assert_eq!(build_dir_entry_path("", "dir"), "/dir/");
449 assert_eq!(build_dir_entry_path("/", "dir"), "/dir/");
450 assert_eq!(build_dir_entry_path("base", "dir"), "base/dir/");
451 assert_eq!(build_dir_entry_path("base/", "dir"), "base/dir/");
452 assert_eq!(build_dir_entry_path("/base", "dir"), "/base/dir/");
453 assert_eq!(build_dir_entry_path("/base/", "dir"), "/base/dir/");
454 }
455
456 #[test]
457 fn test_build_file_entry_path() {
458 assert_eq!(build_file_entry_path("", "file.txt"), "/file.txt");
459 assert_eq!(build_file_entry_path("/", "file.txt"), "/file.txt");
460 assert_eq!(build_file_entry_path("dir", "file.txt"), "dir/file.txt");
461 assert_eq!(build_file_entry_path("dir/", "file.txt"), "dir/file.txt");
462 assert_eq!(build_file_entry_path("/dir", "file.txt"), "/dir/file.txt");
463 assert_eq!(build_file_entry_path("/dir/", "file.txt"), "/dir/file.txt");
464 }
465
466 #[test]
467 fn test_normalize_file_path() {
468 assert_eq!(normalize_file_path(""), "");
469 assert_eq!(normalize_file_path("/"), "/");
470 assert_eq!(normalize_file_path("file.txt"), "/file.txt");
471 assert_eq!(normalize_file_path("/file.txt"), "/file.txt");
472 assert_eq!(normalize_file_path("dir/file.txt"), "/dir/file.txt");
473 assert_eq!(normalize_file_path("/dir/file.txt"), "/dir/file.txt");
474 }
475}