1use std::{
5 collections::BTreeMap,
6 path::{Path, PathBuf},
7};
8
9pub const CACHE_ENV_PREFIX: &str = "HCI_CACHE_";
11
12#[derive(Debug, Clone, Default)]
14pub struct PreparedCaches {
15 pub env: BTreeMap<String, String>,
17 pub dirs: Vec<PathBuf>,
19}
20
21#[must_use]
24pub fn prepare_caches(paths: &[String], cache_root: &Path) -> PreparedCaches {
25 let mut prepared = PreparedCaches::default();
26 let mut used = BTreeMap::<String, u32>::new();
27 for path in paths {
28 let base = slot_name(path);
29 let slot = match used.get_mut(&base) {
30 Some(count) => {
31 *count += 1;
32 format!("{base}_{count}")
33 }
34 None => {
35 used.insert(base.clone(), 0);
36 base
37 }
38 };
39 let directory = cache_root.join(&slot);
40 if std::fs::create_dir_all(&directory).is_err() {
41 continue;
42 }
43 prepared.env.insert(
44 format!("{CACHE_ENV_PREFIX}{slot}"),
45 directory.display().to_string(),
46 );
47 prepared.dirs.push(directory);
48 }
49 prepared
50}
51
52fn slot_name(path: &str) -> String {
53 let mut output = String::with_capacity(path.len());
54 let mut separated = true;
55 for character in path.chars() {
56 if character.is_ascii_alphanumeric() {
57 output.push(character.to_ascii_uppercase());
58 separated = false;
59 } else if !separated {
60 output.push('_');
61 separated = true;
62 }
63 }
64 while output.ends_with('_') {
65 output.pop();
66 }
67 if output.is_empty() {
68 "CACHE".to_string()
69 } else {
70 output
71 }
72}