Skip to main content

textlane_core/helpers/
slug.rs

1use std::collections::HashMap;
2
3use crate::helpers::slug_table::REMOVED;
4
5/// github-slugger's `BananaSlug`: slugs, made unique by counting repeats.
6#[derive(Debug, Default)]
7pub struct Slugger {
8    occurrences: HashMap<String, usize>,
9}
10
11impl Slugger {
12    /// Count `value` as taken, as if a slug had produced it.
13    pub fn reserve(&mut self, value: &str) {
14        self.occurrences.entry(value.to_owned()).or_insert(0);
15    }
16
17    /// A unique slug for `value`: `a`, then `a-1`, `a-2`.
18    pub fn slug(&mut self, value: &str) -> String {
19        let original = create_slug(value);
20        let mut result = original.clone();
21        while self.occurrences.contains_key(&result) {
22            let count = self.occurrences.entry(original.clone()).or_default();
23            *count += 1;
24            result = format!("{original}-{count}");
25        }
26        self.occurrences.insert(result.clone(), 0);
27        result
28    }
29}
30
31/// Lowercase, drop punctuation and symbols, turn spaces into dashes.
32pub fn create_slug(value: &str) -> String {
33    value
34        .to_lowercase()
35        .chars()
36        .filter(|character| !is_removed(*character))
37        .map(|character| if character == ' ' { '-' } else { character })
38        .collect()
39}
40
41fn is_removed(character: char) -> bool {
42    let code = u32::from(character);
43    REMOVED
44        .binary_search_by(|(start, end)| {
45            if *end < code {
46                std::cmp::Ordering::Less
47            } else if *start > code {
48                std::cmp::Ordering::Greater
49            } else {
50                std::cmp::Ordering::Equal
51            }
52        })
53        .is_ok()
54}