use std::sync::{Condvar, Mutex}; pub fn normalize_title(input: &str) -> String { let mut out = String::with_capacity(input.len()); for ch in input.chars() { if ch.is_ascii_alphanumeric() { out.push(ch.to_ascii_lowercase()); } else if ch.is_whitespace() { out.push(' '); } else { out.push(' '); } } collapse_whitespace(&out) } pub fn sanitize_filename(input: &str) -> String { let mut out = String::with_capacity(input.len()); for ch in input.chars() { if matches!(ch, '<' | '>' | ':' | '"' | '/' | '\\' | '|' | '?' | '*') { out.push(' '); } else { out.push(ch); } } collapse_whitespace(&out) } pub fn collapse_whitespace(input: &str) -> String { let mut out = String::with_capacity(input.len()); let mut last_space = false; for ch in input.chars() { if ch.is_whitespace() { if !last_space { out.push(' '); last_space = true; } } else { last_space = false; out.push(ch); } } out.trim().to_string() } pub struct Semaphore { state: Mutex, cvar: Condvar, } impl Semaphore { pub fn new(count: usize) -> Self { Self { state: Mutex::new(count), cvar: Condvar::new(), } } pub fn acquire(&self) -> SemaphoreGuard<'_> { let mut count = self.state.lock().unwrap(); while *count == 0 { count = self.cvar.wait(count).unwrap(); } *count -= 1; SemaphoreGuard { sem: self } } } pub struct SemaphoreGuard<'a> { sem: &'a Semaphore, } impl Drop for SemaphoreGuard<'_> { fn drop(&mut self) { let mut count = self.sem.state.lock().unwrap(); *count += 1; self.sem.cvar.notify_one(); } } #[cfg(test)] mod tests { use super::{collapse_whitespace, normalize_title, sanitize_filename}; #[test] fn normalizes_title() { assert_eq!(normalize_title("The.Matrix!!"), "the matrix"); } #[test] fn sanitizes_filename() { assert_eq!(sanitize_filename("Bad:Name/Here"), "Bad Name Here"); } #[test] fn collapses_whitespace() { assert_eq!(collapse_whitespace("a b c"), "a b c"); } }