Files
mov-renamarr/src/utils.rs
2025-12-30 10:52:59 -05:00

100 lines
2.3 KiB
Rust

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<usize>,
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");
}
}