#[allow(dead_code)]
pub fn email_domain(email: &str) -> &str {
email.split_once('@').map_or("", |(_, d)| d)
}
#[allow(dead_code)]
pub fn abbreviate(text: &str, max: usize) -> String {
if text.len() <= max {
return text.to_owned();
}
let trimmed = &text[..max];
match trimmed.rfind(' ') {
Some(i) if i > 0 => format!("{}...", &trimmed[..i]),
_ => format!("{trimmed}..."),
}
}
#[allow(dead_code)]
pub fn truncate_middle(text: &str, max: usize) -> String {
if text.len() <= max {
return text.to_owned();
}
let sep = "...";
if max <= sep.len() {
return sep[..max].to_owned();
}
let content = max - sep.len();
let start = content / 2;
let end = content - start;
format!("{}{sep}{}", &text[..start], &text[text.len() - end..])
}
#[allow(dead_code)]
pub fn slugify(text: &str, sep: char) -> String {
let s: String = text
.to_lowercase()
.chars()
.map(|c| if c.is_ascii_alphanumeric() { c } else { sep })
.collect();
let sep_str = sep.to_string();
let doubled = format!("{sep}{sep}");
let mut result = s;
while result.contains(&doubled) {
result = result.replace(&doubled, &sep_str);
}
result.trim_matches(sep).to_owned()
}
#[allow(dead_code)]
pub fn levenshtein(a: &str, b: &str) -> usize {
let a: Vec<char> = a.chars().collect();
let b: Vec<char> = b.chars().collect();
let (m, n) = (a.len(), b.len());
let mut prev: Vec<usize> = (0..=n).collect();
let mut curr = vec![0usize; n + 1];
for i in 1..=m {
curr[0] = i;
for j in 1..=n {
let cost = if a[i - 1] == b[j - 1] { 0 } else { 1 };
curr[j] = (prev[j] + 1).min(curr[j - 1] + 1).min(prev[j - 1] + cost);
}
std::mem::swap(&mut prev, &mut curr);
}
prev[n]
}
#[allow(dead_code)]
pub fn similarity(a: &str, b: &str) -> f64 {
if a == b {
return 1.0;
}
let max_len = a.len().max(b.len());
if max_len == 0 {
return 1.0;
}
1.0 - (levenshtein(a, b) as f64 / max_len as f64)
}
#[allow(dead_code)]
pub fn best_match<'a>(target: &str, candidates: &'a [String], threshold: f64) -> Option<&'a str> {
let target_lower = target.to_lowercase();
candidates
.iter()
.filter_map(|c| {
let ratio = similarity(&target_lower, &c.to_lowercase());
if ratio >= threshold {
Some((c.as_str(), ratio))
} else {
None
}
})
.max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
.map(|(s, _)| s)
}