1pub fn normalize_key(key: &str) -> String {
2 let mut out = String::with_capacity(key.len());
3 let mut last_type = 0; for ch in key.trim().chars() {
6 if ch.is_ascii_alphanumeric() {
7 out.push(ch);
8 last_type = 0;
9 } else if ch == ':' {
10 if last_type != 0 {
11 out.pop();
12 }
13 out.push(':');
14 last_type = 2;
15 } else if matches!(ch, '-' | '_' | '.') || ch.is_whitespace() {
16 let connector = if ch.is_whitespace() { '-' } else { ch };
17 if last_type == 0 {
18 out.push(connector);
19 last_type = 1;
20 }
21 }
23 }
24 out.trim_matches(|c| c == '-' || c == ':' || c == '_' || c == '.')
25 .to_string()
26}
27
28fn fnv1a(s: &str) -> u64 {
29 let mut h: u64 = 0xcbf29ce484222325;
30 for b in s.as_bytes() {
31 h ^= *b as u64;
32 h = h.wrapping_mul(0x100000001b3);
33 }
34 h
35}
36
37pub fn slugify(text: &str) -> String {
38 let slug = text
39 .to_lowercase()
40 .chars()
41 .map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
42 .collect::<String>()
43 .split('-')
44 .filter(|s| !s.is_empty())
45 .collect::<Vec<_>>()
46 .join("-");
47
48 if slug.is_empty() {
49 format!("t-{:016x}", fnv1a(text))
50 } else {
51 slug
52 }
53}
54
55#[cfg(test)]
56#[path = "normalize_test.rs"]
57mod tests;