use lazy_static::lazy_static;
use regex::Regex;
pub const HASH_PRIME: u64 = 0x100000001B3;
pub const HASH_BASIS: u64 = 0xCBF29CE484222325;
pub fn hash(s: &str) -> u64 {
let mut result = HASH_BASIS;
for &byte in s.as_bytes() {
result ^= byte as u64;
result = result.wrapping_mul(HASH_PRIME);
}
result
}
pub const fn hash_const(s: &str) -> u64 {
let bytes = s.as_bytes();
let mut result = HASH_BASIS;
let mut i = 0;
while i < bytes.len() {
result ^= bytes[i] as u64;
result = result.wrapping_mul(HASH_PRIME);
i += 1;
}
result
}
pub const fn hash_compile_time(s: &str) -> u64 {
hash_const(s)
}
pub fn replace_all_distinct(s: &str, from: &str, to: &str) -> String {
let mut result = s.to_string();
let mut position = 0;
while let Some(found_pos) = result[position..].find(from) {
let absolute_pos = position + found_pos;
result.replace_range(absolute_pos..absolute_pos + from.len(), to);
position = absolute_pos + to.len();
}
result
}
pub fn starts_with(s: &str, prefix: &str) -> bool {
s.starts_with(prefix)
}
pub fn ends_with(s: &str, suffix: &str) -> bool {
s.ends_with(suffix)
}
pub fn to_lower(s: &str) -> String {
s.to_lowercase()
}
pub fn trim(s: &str) -> &str {
s.trim()
}
pub fn trim_whitespace(s: &str, before: bool, after: bool) -> String {
if before {
s.trim_start().to_string()
} else if after {
s.trim_end().to_string()
} else {
s.trim().to_string()
}
}
pub fn trim_of(s: &str, target: char, before: bool, after: bool) -> String {
if !before && !after {
return s.to_string();
}
let len = s.len();
if len == 0 {
return s.to_string();
}
let mut start = 0;
let mut end = len;
if before {
for (i, ch) in s.char_indices() {
if ch != target {
start = i;
break;
}
}
}
if after {
for (i, ch) in s.char_indices().rev() {
if ch != target {
end = i + ch.len_utf8();
break;
}
}
}
if start >= end {
return String::new();
}
s[start..end].to_string()
}
pub fn find_str(s: &str, search: &str) -> Option<usize> {
s.find(search)
}
pub fn join<T: AsRef<str>>(parts: &[T], separator: &str) -> String {
parts
.iter()
.map(|s| s.as_ref())
.collect::<Vec<&str>>()
.join(separator)
}
lazy_static! {
static ref EMOJI_REGEX: Regex = Regex::new(r"\p{Emoji_Presentation}|\p{Extended_Pictographic}").unwrap();
}
pub fn remove_emoji(s: &str) -> String {
EMOJI_REGEX.replace_all(s, "").into_owned()
}
pub fn md5(input: &str) -> String {
use md5::{Digest, Md5};
let mut hasher = Md5::new();
hasher.update(input.as_bytes());
let result = hasher.finalize();
let mut hex_string = String::with_capacity(32);
for byte in result.iter() {
hex_string.push_str(&format!("{:02x}", byte));
}
hex_string
}
pub fn join_path(base: &str, segment: &str) -> String {
if base.is_empty() {
return segment.to_string();
}
let base_has_trailing_slash = base.ends_with('/');
let segment_has_leading_slash = segment.starts_with('/');
match (base_has_trailing_slash, segment_has_leading_slash) {
(true, true) => format!("{}{}", base, &segment[1..]),
(false, false) => format!("{}/{}", base, segment),
(true, false) => format!("{}{}", base, segment),
(false, true) => format!("{}{}", base, segment),
}
}
pub fn normalize_dir_path(path: &str) -> String {
if path.is_empty() {
return String::new();
}
if path.ends_with('/') {
path.to_string()
} else {
format!("{}/", path)
}
}
pub fn build_dir_entry_path(base_path: &str, dir_name: &str) -> String {
let base = normalize_dir_path(base_path);
if base.is_empty() {
format!("/{}/", dir_name)
} else {
join_path(&base, &format!("{}/", dir_name))
}
}
pub fn build_file_entry_path(base_path: &str, file_name: &str) -> String {
if base_path.is_empty() {
format!("/{}", file_name)
} else {
join_path(base_path, file_name)
}
}
pub fn normalize_file_path(path: &str) -> String {
if path.is_empty() {
return String::new();
}
if path.starts_with('/') {
path.to_string()
} else {
format!("/{}", path)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hash() {
assert_eq!(hash("test"), 18007334074686647077);
assert_eq!(hash("hello"), 11831194018420276491);
assert_eq!(hash(""), HASH_BASIS);
}
#[test]
fn test_hash_const() {
assert_eq!(hash_const("test"), hash("test"));
assert_eq!(hash_const("hello"), hash("hello"));
assert_eq!(hash_const(""), HASH_BASIS);
}
#[test]
fn test_replace_all_distinct() {
assert_eq!(replace_all_distinct("hello world", "o", "0"), "hell0 w0rld");
assert_eq!(replace_all_distinct("test-test", "-", "_"), "test_test");
assert_eq!(replace_all_distinct("abcabc", "a", "x"), "xbcxbc");
}
#[test]
fn test_starts_with() {
assert!(starts_with("hello world", "hello"));
assert!(!starts_with("hello world", "world"));
}
#[test]
fn test_ends_with() {
assert!(ends_with("hello world", "world"));
assert!(!ends_with("hello world", "hello"));
}
#[test]
fn test_to_lower() {
assert_eq!(to_lower("HELLO"), "hello");
assert_eq!(to_lower("Hello World"), "hello world");
}
#[test]
fn test_trim() {
assert_eq!(trim(" hello "), "hello");
assert_eq!(trim("\t\nhello\r\n"), "hello");
}
#[test]
fn test_join() {
let parts = vec!["a", "b", "c"];
assert_eq!(join(&parts, ","), "a,b,c");
assert_eq!(join(&parts, ""), "abc");
assert_eq!(join(&parts, " - "), "a - b - c");
let empty: Vec<&str> = vec![];
assert_eq!(join(&empty, ","), "");
}
#[test]
fn test_remove_emoji() {
assert_eq!(remove_emoji("😀Hello"), "Hello");
assert_eq!(remove_emoji("😀😁Hello"), "Hello");
assert_eq!(remove_emoji("Hello"), "Hello");
assert_eq!(remove_emoji("😀"), "😀"); }
#[test]
fn test_md5() {
assert_eq!(md5(""), "d41d8cd98f00b204e9800998ecf8427e");
assert_eq!(md5("hello world"), "5eb63bbbe01eeed093cb22bb8f5acdc3");
assert_eq!(md5("test"), "098f6bcd4621d373cade4e832627b4f6");
}
#[test]
fn test_join_path() {
assert_eq!(join_path("", "file.txt"), "file.txt");
assert_eq!(join_path("/", "file.txt"), "/file.txt");
assert_eq!(join_path("dir", "file.txt"), "dir/file.txt");
assert_eq!(join_path("dir/", "file.txt"), "dir/file.txt");
assert_eq!(join_path("dir", "/file.txt"), "dir/file.txt");
assert_eq!(join_path("dir/", "/file.txt"), "dir/file.txt");
assert_eq!(join_path("/dir", "subdir/file.txt"), "/dir/subdir/file.txt");
}
#[test]
fn test_normalize_dir_path() {
assert_eq!(normalize_dir_path(""), "");
assert_eq!(normalize_dir_path("/"), "/");
assert_eq!(normalize_dir_path("dir"), "dir/");
assert_eq!(normalize_dir_path("dir/"), "dir/");
assert_eq!(normalize_dir_path("/dir"), "/dir/");
assert_eq!(normalize_dir_path("/dir/"), "/dir/");
}
#[test]
fn test_build_dir_entry_path() {
assert_eq!(build_dir_entry_path("", "dir"), "/dir/");
assert_eq!(build_dir_entry_path("/", "dir"), "/dir/");
assert_eq!(build_dir_entry_path("base", "dir"), "base/dir/");
assert_eq!(build_dir_entry_path("base/", "dir"), "base/dir/");
assert_eq!(build_dir_entry_path("/base", "dir"), "/base/dir/");
assert_eq!(build_dir_entry_path("/base/", "dir"), "/base/dir/");
}
#[test]
fn test_build_file_entry_path() {
assert_eq!(build_file_entry_path("", "file.txt"), "/file.txt");
assert_eq!(build_file_entry_path("/", "file.txt"), "/file.txt");
assert_eq!(build_file_entry_path("dir", "file.txt"), "dir/file.txt");
assert_eq!(build_file_entry_path("dir/", "file.txt"), "dir/file.txt");
assert_eq!(build_file_entry_path("/dir", "file.txt"), "/dir/file.txt");
assert_eq!(build_file_entry_path("/dir/", "file.txt"), "/dir/file.txt");
}
#[test]
fn test_normalize_file_path() {
assert_eq!(normalize_file_path(""), "");
assert_eq!(normalize_file_path("/"), "/");
assert_eq!(normalize_file_path("file.txt"), "/file.txt");
assert_eq!(normalize_file_path("/file.txt"), "/file.txt");
assert_eq!(normalize_file_path("dir/file.txt"), "/dir/file.txt");
assert_eq!(normalize_file_path("/dir/file.txt"), "/dir/file.txt");
}
}