use anyhow::Result;
pub fn generate_slug(
text: &str,
separator: Option<&str>,
max_length: Option<usize>,
lower_case: Option<bool>,
) -> String {
let separator = separator.unwrap_or("-");
let max_length = max_length.unwrap_or(100);
let lower_case = lower_case.unwrap_or(true);
if text.is_empty() {
return String::new();
}
let text = if lower_case {
text.to_lowercase()
} else {
text.to_string()
};
let mut result = String::new();
let mut last_was_separator = false;
for c in text.chars() {
if c.is_alphanumeric() {
result.push(c);
last_was_separator = false;
} else if c == ' ' || c == '-' || c == '_' || c == '.' {
if !last_was_separator && !result.is_empty() {
result.push_str(separator);
last_was_separator = true;
}
} else if c == '\'' {
continue;
} else {
if !last_was_separator && !result.is_empty() {
result.push_str(separator);
last_was_separator = true;
}
}
}
if result.ends_with(separator) {
result.truncate(result.len() - separator.len());
}
if result.starts_with(separator) {
result = result[separator.len()..].to_string();
}
if result.len() > max_length {
let mut cut_at = max_length;
while cut_at > 0 && !result.chars().nth(cut_at).unwrap_or(' ').is_alphanumeric() {
cut_at -= 1;
}
if cut_at == 0 {
cut_at = max_length;
}
result = result[..cut_at].to_string();
if result.ends_with(separator) {
result.truncate(result.len() - separator.len());
}
}
result
}
pub fn slug(text: &str) -> String {
generate_slug(text, None, None, None)
}
pub fn slug_with_separator(text: &str, separator: &str) -> String {
generate_slug(text, Some(separator), None, None)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_slug_basic() {
assert_eq!(slug("Hello World"), "hello-world");
assert_eq!(slug("Hello World!"), "hello-world");
assert_eq!(slug(" Hello World "), "hello-world");
}
#[test]
fn test_slug_special_chars() {
assert_eq!(slug("Hello, World!"), "hello-world");
assert_eq!(slug("Rust & C++"), "rust-c");
assert_eq!(slug("Email@example.com"), "email-example-com");
assert_eq!(slug("Price: $100"), "price-100");
}
#[test]
fn test_slug_unicode() {
assert_eq!(slug("Привет мир"), "привет-мир");
assert_eq!(slug("Hello 世界"), "hello-世界");
}
#[test]
fn test_slug_empty() {
assert_eq!(slug(""), "");
assert_eq!(slug(" "), "");
}
#[test]
fn test_slug_separator() {
assert_eq!(slug_with_separator("Hello World", "_"), "hello_world");
assert_eq!(slug_with_separator("Hello World", "."), "hello.world");
assert_eq!(slug_with_separator("Hello World", ""), "helloworld");
}
#[test]
fn test_slug_max_length() {
let long = "a".repeat(200);
let result = generate_slug(&long, None, Some(50), None);
assert_eq!(result.len(), 50);
assert_eq!(result, "a".repeat(50));
}
#[test]
fn test_slug_max_length_with_cut() {
let text = "Hello World This Is A Test";
let result = generate_slug(text, None, Some(15), None);
assert!(result.len() <= 15);
assert!(!result.ends_with('-'));
}
#[test]
fn test_slug_preserve_case() {
assert_eq!(generate_slug("Hello World", None, None, Some(false)), "Hello-World");
assert_eq!(generate_slug("Hello World", None, None, Some(true)), "hello-world");
}
#[test]
fn test_slug_only_special_chars() {
assert_eq!(slug("!@#$%^&*()"), "");
assert_eq!(slug("---___..."), "");
}
#[test]
fn test_slug_numbers() {
assert_eq!(slug("123 Main St"), "123-main-st");
assert_eq!(slug("Version 2.0"), "version-2-0");
}
#[test]
fn test_slug_apostrophe() {
assert_eq!(slug("John's Blog"), "johns-blog");
assert_eq!(slug("Don't Stop"), "dont-stop");
}
#[test]
fn test_slug_ampersand() {
assert_eq!(slug("Rock & Roll"), "rock-roll");
assert_eq!(slug("Tom & Jerry"), "tom-jerry");
}
#[test]
fn test_slug_multiple_separators() {
assert_eq!(slug("Hello---World"), "hello-world");
assert_eq!(slug("Hello...World"), "hello-world");
assert_eq!(slug("Hello___World"), "hello-world");
}
}