1#![deny(missing_docs, rust_2018_idioms, elided_lifetimes_in_paths)]
16#![crate_name = "el_slugify"]
17
18use deunicode::deunicode_char;
19
20pub fn slugify(value: &str) -> String {
30 slugify_with_replacement(value, '-')
31}
32
33pub fn slugify_with_replacement(value: &str, replacement: char) -> String {
35 trim_trailing_space(sanitize(value, replacement).to_lowercase().as_str(), replacement)
36}
37
38fn sanitize(value: &str, replacement: char) -> String {
40 let mut out = String::new();
41 for elem in value.chars() {
42 if is_contained_in_limited_set(elem) {
43 out.push(elem)
44 } else if elem.is_alphabetic() {
45 let decoded_elem = deunicode_char(elem).map(|d| sanitize(d, replacement));
47 if let Some(decoded) = decoded_elem {
48 out.push_str(&decoded);
49 }
50 } else if !out.ends_with(replacement) {
51 out.push(replacement)
52 }
53 }
54
55 out.to_string()
56}
57
58fn is_contained_in_limited_set(value: char) -> bool {
59 matches!(value, '0'..='9' | 'a'..='z' | 'A'..='Z')
60}
61
62fn trim_trailing_space(value: &str, replacement: char) -> String {
63 let mut check_value = value.to_string();
64 if check_value.starts_with(replacement) {
65 check_value.remove(0);
66 }
67 if check_value.ends_with(replacement) {
68 check_value.pop();
69 }
70 check_value.to_string()
71}
72
73#[cfg(test)]
74mod tests {
75 use std::time::Instant;
76 use slugify::slugify;
77 use crate::{slugify as el_slugify};
78
79 #[test]
80 fn slugify_long_special_chars_wor2() {
81 let binding = std::iter::repeat("#% MaČKA mački grize rep! (RIB-a) ~*")
82 .take(10000).collect::<String>();
83
84 let start = Instant::now();
85 let _ = el_slugify(binding.as_str());
86 let elapsed = start.elapsed();
87 println!(">> El slugify took: {:?}", elapsed);
88
89 let start = Instant::now();
90 let _ = slugify!(binding.as_str());
91 let elapsed = start.elapsed();
92 println!(">> Slugify took: {:?}", elapsed);
93 }
94
95 #[test]
96 fn test_slugify() {
97 assert_eq!(el_slugify("Wait! Listen \n\t!!!Runagalðr$ ~"), "wait-listen-runagaldr");
98 assert_eq!(el_slugify("影 _ 師 嗎"), "ying-shi-ma");
99 assert_eq!(el_slugify("影師嗎"), "ying-shi-ma");
100 assert_eq!(el_slugify("di su ćevapi?"), "di-su-cevapi");
101 assert_eq!(el_slugify("!-.#"), "");
102 assert_eq!(el_slugify("! kako a je"), "kako-a-je");
103 assert_eq!(el_slugify("iako ** .+a na"), "iako-a-na");
104 assert_eq!(el_slugify(" -!abc"), "abc");
105 assert_eq!(el_slugify("iako *."), "iako");
106 assert_eq!(el_slugify("dakako"), "dakako"); assert_eq!(el_slugify(" -!kako tako _:+"), "kako-tako");
108 assert_eq!(el_slugify(""), "");
109 assert_eq!(el_slugify("!-.#"), "");
110 }
111}