pub fn truncate(s: &str, max: usize) -> String {
if s.chars().count() <= max {
s.to_string()
} else {
let mut out: String = s.chars().take(max.saturating_sub(1)).collect();
out.push('…');
out
}
}
pub fn summarize(text: &str) -> String {
const MAX: usize = 80;
let trimmed = text.trim();
if trimmed.chars().count() <= MAX {
trimmed.to_string()
} else {
let truncated: String = trimmed.chars().take(MAX).collect();
format!("{}…", truncated.trim_end())
}
}
pub fn plural(n: usize) -> &'static str {
if n == 1 { "" } else { "s" }
}
pub fn strength_label(s: f64) -> &'static str {
if s >= 2.0 {
"Strong"
} else if s >= 1.5 {
"Linked"
} else {
"Weak"
}
}
pub fn split_csv(s: &str) -> Vec<String> {
s.split(',')
.map(str::trim)
.filter(|s| !s.is_empty())
.map(str::to_string)
.collect()
}
pub fn normalize_list(values: &[String]) -> Vec<String> {
values
.iter()
.map(|v| v.trim())
.filter(|v| !v.is_empty())
.map(str::to_string)
.collect()
}
pub fn short_id(s: &str) -> String {
s.chars().take(8).collect()
}