malvin 0.2.4

Non-interactive research and coding agent
use std::io::ErrorKind;
use std::path::{Path, PathBuf};

pub(crate) struct DotfileBackupLabels {
    pub mkdir: &'static str,
    pub collision: &'static str,
    pub restore: &'static str,
}

pub(crate) fn allocate_backup_dir(
    root: &Path,
    generate_id: &mut impl FnMut(usize) -> String,
    labels: &DotfileBackupLabels,
) -> Result<PathBuf, String> {
    std::fs::create_dir_all(root).map_err(|e| format!("{}: {e}", labels.mkdir))?;
    let mut tries = 0usize;
    while tries < 16 {
        let candidate = root.join(generate_id(tries));
        match std::fs::create_dir(&candidate) {
            Ok(()) => return Ok(candidate),
            Err(err) if err.kind() == ErrorKind::AlreadyExists => {
                tries += 1;
            }
            Err(err) => return Err(format!("{}: {err}", labels.mkdir)),
        }
    }
    Err(format!("{}: too many id collisions", labels.collision))
}

pub(crate) fn remove_if_exists(path: &Path, restore_label: &str) -> Result<(), String> {
    if path.exists() {
        let metadata = std::fs::metadata(path).map_err(|e| format!("{restore_label}: {e}"))?;
        if metadata.is_dir() {
            std::fs::remove_dir_all(path).map_err(|e| format!("{restore_label}: {e}"))?;
        } else {
            std::fs::remove_file(path).map_err(|e| format!("{restore_label}: {e}"))?;
        }
    }
    Ok(())
}

pub(crate) fn random_backup_id(_try_index: usize) -> String {
    use rand::Rng;
    const ALPHABET: &[u8] = b"abcdefghijklmnopqrstuvwxyz0123456789";
    let mut rng = rand::thread_rng();
    (0..5)
        .map(|_| {
            let i = rng.gen_range(0..ALPHABET.len());
            ALPHABET[i] as char
        })
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn allocate_backup_dir_creates_distinct_directories() {
        let tmp = tempfile::tempdir().expect("tempdir");
        let labels = DotfileBackupLabels {
            mkdir: "mkdir",
            collision: "collision",
            restore: "restore",
        };
        let mut next_id = random_backup_id;
        let first = allocate_backup_dir(tmp.path(), &mut next_id, &labels).expect("first");
        let second = allocate_backup_dir(tmp.path(), &mut next_id, &labels).expect("second");
        assert!(first.is_dir());
        assert!(second.is_dir());
        assert_ne!(first, second);
    }
}

#[cfg(test)]
mod kiss_cov_auto {
    use super::*;
    use crate::user_home_dir;

    #[test]
    fn kiss_cov_malvin_home_dir() {
        let _ = user_home_dir;
    }

    #[test]
    fn kiss_cov_dotfile_backup_labels() {
        let _: Option<DotfileBackupLabels> = None;
    }

    #[test]
    fn kiss_cov_allocate_backup_dir() {
        let tmp = tempfile::tempdir().expect("tempdir");
        let labels = DotfileBackupLabels {
            mkdir: "m",
            collision: "c",
            restore: "r",
        };
        let mut id = random_backup_id;
        let _ = allocate_backup_dir(tmp.path(), &mut id, &labels).expect("alloc");
    }

    #[test]
    fn kiss_cov_remove_if_exists() {
        let _ = remove_if_exists;
    }

    #[test]
    fn kiss_cov_random_backup_id() {
        let _ = random_backup_id;
    }
}

#[cfg(test)]
#[allow(unused_imports)]
mod kiss_cov_gate_refs {
    use super::*;
    use crate::user_home_dir;
    #[test]
    fn kiss_cov_unit_names() {
        let _labels = DotfileBackupLabels {
            mkdir: "mkdir",
            collision: "collision",
            restore: "restore",
        };
        let _tmp = tempfile::tempdir().expect("tempdir");
        let _ = user_home_dir;
        let _ = stringify!(allocate_backup_dir);
        let _ = random_backup_id;
        let _ = remove_if_exists;
    }
}