1pub fn target_file_name(file_name: &str, tag_name: Option<&str>) -> String {
2 let (name, _, ext) = split_file_name(file_name);
3 let mut result = String::new();
4 result.push_str(name);
5 if let Some(tag_name) = tag_name {
6 result.push('-');
7 result.push_str(tag_name);
8 }
9 if let Some(ext) = ext {
10 result.push('.');
11 result.push_str(ext);
12 }
13
14 result
15}
16
17pub fn split_file_name(input: &str) -> (&str, Option<&str>, Option<&str>) {
26 const EXTENSIONS: [&str; 4] = [".exe", ".so", ".dylib", ".dll"];
27
28 const RUSTC_HASH_LENGTH: usize = 16;
29
30 let mut file_name = input;
31 let mut extension = None;
32 for ext in EXTENSIONS {
33 if let Some(name) = input.strip_suffix(ext) {
34 file_name = name;
35 extension = Some(&ext[1..]);
36 break;
37 }
38 }
39
40 let idx = match file_name.rfind('-') {
41 Some(idx) if idx > 0 => idx,
42 _ => return (file_name, None, extension),
43 };
44
45 let hash = &file_name[idx + 1..];
46 if hash.len() == RUSTC_HASH_LENGTH && hash.chars().all(|c| c.is_ascii_hexdigit()) {
49 (&file_name[..idx], Some(hash), extension)
50 } else {
51 (file_name, None, extension)
52 }
53}
54
55#[cfg(test)]
56mod test {
57 use super::*;
58
59 #[test]
60 fn check_target_file_name() {
61 let cases = vec![
62 ("app-ebb8dd5b587f73a1", None, "app"),
64 ("app-ebb8dd5b587f73a1", Some("tag"), "app-tag"),
65 ("app-ebb8dd5b587f73a1.exe", None, "app.exe"),
67 ("app-ebb8dd5b587f73a1.dll", None, "app.dll"),
68 ("app-ebb8dd5b587f73a1.exe", Some("tag"), "app-tag.exe"),
69 ];
70
71 for (input, tag, expected) in cases {
72 assert_eq!(target_file_name(input, tag), expected);
73 }
74 }
75}