//! URL slug helpers shared by the catalog admin (products and categories). use loco_rs::prelude::*; /// Lowercase a string and collapse every run of non-alphanumeric characters /// into a single dash, trimming dashes from the ends. pub fn slugify(value: &str) -> String { let mut slug = String::new(); let mut last_was_dash = false; for ch in value.chars().flat_map(char::to_lowercase) { if ch.is_ascii_alphanumeric() { slug.push(ch); last_was_dash = false; } else if !last_was_dash && !slug.is_empty() { slug.push('-'); last_was_dash = true; } } slug.trim_matches('-').to_string() } /// Find the first slug that does not already exist, appending `-2`, `-3`, … to /// `base` until `exists` reports the candidate as free. An empty `base` falls /// back to `"item"`. pub async fn unique_slug(base: &str, mut exists: F) -> Result where F: FnMut(String) -> Fut, Fut: std::future::Future>, { let base = if base.is_empty() { "item" } else { base }; let mut slug = base.to_string(); let mut suffix = 2; while exists(slug.clone()).await? { slug = format!("{base}-{suffix}"); suffix += 1; } Ok(slug) }