use std::path::Path;
pub fn shuffle<T>(items: &mut [T]) {
let mut seed = [0u8; 8];
if getrandom::fill(&mut seed).is_err() {
return; }
let mut state = u64::from_le_bytes(seed) | 1;
for i in (1..items.len()).rev() {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
items.swap(i, (state % (i as u64 + 1)) as usize);
}
}
pub fn truncate_bytes(s: &str, max: usize) -> &str {
if s.len() <= max {
return s;
}
let mut end = max;
while end > 0 && !s.is_char_boundary(end) {
end -= 1;
}
&s[..end]
}
pub fn sanitise_filename(s: &str) -> String {
let cleaned: String = s
.chars()
.map(|c| match c {
'/' | '\\' | ':' | '*' | '?' | '"' | '<' | '>' | '|' => '_',
_ => c,
})
.collect::<String>()
.trim()
.to_string();
let cleaned = truncate_bytes(&cleaned, 240).trim_end().to_string();
match cleaned.as_str() {
"." | ".." => "_".into(),
_ => cleaned,
}
}
pub fn sanitise_extension(codec: &str) -> Option<String> {
let ext: String = codec
.chars()
.filter(char::is_ascii_alphanumeric)
.take(16)
.collect::<String>()
.to_lowercase();
(!ext.is_empty()).then_some(ext)
}
pub fn path_within(dir: &Path, path: &Path) -> bool {
path.strip_prefix(dir).is_ok_and(|rest| {
rest.components()
.all(|c| matches!(c, std::path::Component::Normal(_)))
})
}
pub fn year_of(date: &str) -> Option<&str> {
date.get(..4)
}
#[cfg(test)]
mod year_tests {
use super::year_of;
#[test]
fn a_year_is_the_first_four_characters_when_they_are_bytes_too() {
assert_eq!(year_of("1997-05-21"), Some("1997"));
assert_eq!(year_of("199"), None);
assert_eq!(year_of("1997"), None);
}
}