Skip to main content

miku/
normalize.rs

1pub fn normalize_key(key: &str) -> String {
2    let mut out = String::with_capacity(key.len());
3    let mut last_type = 0; // 0: None/Alpha, 1: Connector, 2: Separator (:)
4
5    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            // If last_type is 1 or 2, we ignore additional connectors
22        }
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;