| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
use super::profile::{NamingCase, NamingRules}; |
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
pub fn sanitize_filename(name: &str, rules: &NamingRules) -> String { |
| 14 |
let mut result = String::with_capacity(name.len()); |
| 15 |
|
| 16 |
for ch in name.chars() { |
| 17 |
if ch.is_ascii_alphanumeric() { |
| 18 |
result.push(ch); |
| 19 |
} else if ch == ' ' || ch == '-' || ch == '_' || ch == '.' { |
| 20 |
|
| 21 |
|
| 22 |
if !result.ends_with(rules.separator) { |
| 23 |
result.push(rules.separator); |
| 24 |
} |
| 25 |
} else if !rules.strip_special { |
| 26 |
result.push(ch); |
| 27 |
} |
| 28 |
|
| 29 |
} |
| 30 |
|
| 31 |
|
| 32 |
let trimmed = result.trim_matches(rules.separator); |
| 33 |
let mut result = trimmed.to_string(); |
| 34 |
|
| 35 |
|
| 36 |
result = match rules.case { |
| 37 |
NamingCase::Lower => result.to_lowercase(), |
| 38 |
NamingCase::Upper => result.to_uppercase(), |
| 39 |
NamingCase::Original => result, |
| 40 |
}; |
| 41 |
|
| 42 |
|
| 43 |
if result.is_empty() { |
| 44 |
result = "untitled".to_string(); |
| 45 |
} |
| 46 |
|
| 47 |
|
| 48 |
if result.len() > rules.max_length { |
| 49 |
|
| 50 |
let mut end = rules.max_length; |
| 51 |
while !result.is_char_boundary(end) && end > 0 { |
| 52 |
end -= 1; |
| 53 |
} |
| 54 |
|
| 55 |
let truncated = result[..end].trim_end_matches(rules.separator); |
| 56 |
result = truncated.to_string(); |
| 57 |
} |
| 58 |
|
| 59 |
result |
| 60 |
} |
| 61 |
|
| 62 |
#[cfg(test)] |
| 63 |
mod tests { |
| 64 |
use super::*; |
| 65 |
|
| 66 |
fn rules(case: NamingCase, sep: char, max_len: usize, strip: bool) -> NamingRules { |
| 67 |
NamingRules { |
| 68 |
case, |
| 69 |
separator: sep, |
| 70 |
max_length: max_len, |
| 71 |
strip_special: strip, |
| 72 |
} |
| 73 |
} |
| 74 |
|
| 75 |
#[test] |
| 76 |
fn basic_uppercase() { |
| 77 |
let r = rules(NamingCase::Upper, '_', 12, true); |
| 78 |
assert_eq!(sanitize_filename("my kick 01", &r), "MY_KICK_01"); |
| 79 |
} |
| 80 |
|
| 81 |
#[test] |
| 82 |
fn basic_lowercase() { |
| 83 |
let r = rules(NamingCase::Lower, '_', 64, true); |
| 84 |
assert_eq!(sanitize_filename("My_Sample", &r), "my_sample"); |
| 85 |
} |
| 86 |
|
| 87 |
#[test] |
| 88 |
fn strips_special_chars() { |
| 89 |
let r = rules(NamingCase::Original, '_', 64, true); |
| 90 |
assert_eq!(sanitize_filename("kick (boom)!!", &r), "kick_boom"); |
| 91 |
} |
| 92 |
|
| 93 |
#[test] |
| 94 |
fn keeps_special_when_not_stripping() { |
| 95 |
let r = rules(NamingCase::Original, '_', 64, false); |
| 96 |
assert_eq!(sanitize_filename("kick(v2)", &r), "kick(v2)"); |
| 97 |
} |
| 98 |
|
| 99 |
#[test] |
| 100 |
fn truncates_to_max_length() { |
| 101 |
let r = rules(NamingCase::Upper, '_', 8, true); |
| 102 |
assert_eq!(sanitize_filename("very long sample name", &r), "VERY_LON"); |
| 103 |
} |
| 104 |
|
| 105 |
#[test] |
| 106 |
fn truncate_avoids_trailing_separator() { |
| 107 |
let r = rules(NamingCase::Upper, '_', 5, true); |
| 108 |
|
| 109 |
assert_eq!(sanitize_filename("ab cd ef", &r), "AB_CD"); |
| 110 |
} |
| 111 |
|
| 112 |
#[test] |
| 113 |
fn collapses_consecutive_separators() { |
| 114 |
let r = rules(NamingCase::Lower, '_', 64, true); |
| 115 |
assert_eq!(sanitize_filename("a - - b", &r), "a_b"); |
| 116 |
} |
| 117 |
|
| 118 |
#[test] |
| 119 |
fn empty_input() { |
| 120 |
let r = rules(NamingCase::Lower, '_', 64, true); |
| 121 |
assert_eq!(sanitize_filename("", &r), "untitled"); |
| 122 |
} |
| 123 |
|
| 124 |
#[test] |
| 125 |
fn only_special_chars_stripped() { |
| 126 |
let r = rules(NamingCase::Lower, '_', 64, true); |
| 127 |
assert_eq!(sanitize_filename("!!!", &r), "untitled"); |
| 128 |
} |
| 129 |
|
| 130 |
#[test] |
| 131 |
fn dot_separator_in_name() { |
| 132 |
let r = rules(NamingCase::Upper, '_', 12, true); |
| 133 |
assert_eq!(sanitize_filename("kick.v2.hard", &r), "KICK_V2_HARD"); |
| 134 |
} |
| 135 |
} |
| 136 |
|