#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;
#[cfg_attr(feature = "wasm", wasm_bindgen)]
pub fn slugify(name: &str) -> String {
let lower = name.to_lowercase();
let stripped: String = lower
.chars()
.map(|c| {
if c.is_alphanumeric() || c == ' ' || c == '-' || c == '_' {
c
} else {
' '
}
})
.collect();
let parts: Vec<&str> = stripped.split_whitespace().collect();
let joined = parts.join("-");
let mut result = String::with_capacity(joined.len());
let mut prev_hyphen = false;
for c in joined.chars() {
if c == '-' {
if !prev_hyphen {
result.push('-');
}
prev_hyphen = true;
} else {
result.push(c);
prev_hyphen = false;
}
}
let trimmed = result.trim_matches('-');
if trimmed.len() <= 80 {
trimmed.to_string()
} else {
let mut end = 80;
while !trimmed.is_char_boundary(end) {
end -= 1;
}
trimmed[..end].trim_matches('-').to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn slugify_basic() {
assert_eq!(slugify("Hello World"), "hello-world");
}
#[test]
fn slugify_special_chars() {
assert_eq!(slugify("Rust & TypeScript!"), "rust-typescript");
}
#[test]
fn slugify_leading_trailing_spaces() {
assert_eq!(slugify(" my doc "), "my-doc");
}
#[test]
fn slugify_multiple_spaces() {
assert_eq!(slugify("my big doc"), "my-big-doc");
}
#[test]
fn slugify_empty() {
assert_eq!(slugify(""), "");
}
#[test]
fn slugify_only_special_chars() {
assert_eq!(slugify("!!!"), "");
}
#[test]
fn slugify_numbers() {
assert_eq!(slugify("My Doc 2024"), "my-doc-2024");
}
#[test]
fn slugify_existing_hyphens() {
assert_eq!(slugify("my-doc"), "my-doc");
}
#[test]
fn slugify_truncates_at_80() {
let long = "a".repeat(100);
let result = slugify(&long);
assert_eq!(result.len(), 80);
}
#[test]
fn slugify_byte_identity_vs_ts_vec1() {
assert_eq!(slugify("Hello World"), "hello-world");
}
#[test]
fn slugify_byte_identity_vs_ts_vec2() {
assert_eq!(slugify("My Collection 2024"), "my-collection-2024");
}
#[test]
fn slugify_byte_identity_vs_ts_vec3() {
assert_eq!(slugify(" LLMtxt Docs "), "llmtxt-docs");
}
}