use std::path::{Component, Path, PathBuf};
use anyhow::{Context, Result};
use crate::prot::{self, ProtData};
pub fn backup_file(home: &Path, cwd: &Path) -> PathBuf {
let mut path = home.join(".prot").join("backups");
for comp in cwd.components() {
match comp {
Component::Normal(name) => path.push(name),
Component::Prefix(prefix) => {
let name = sanitize_prefix(&prefix.as_os_str().to_string_lossy());
if !name.is_empty() {
path.push(name);
}
}
_ => {}
}
}
path.join(crate::commands::PROT_FILE)
}
fn sanitize_prefix(prefix: &str) -> String {
prefix
.chars()
.filter_map(|c| {
if c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '.') {
Some(c)
} else if c == ':' {
None
} else {
Some('_')
}
})
.collect::<String>()
.trim_matches('_')
.to_string()
}
pub fn save(home: &Path, cwd: &Path, data: &ProtData) -> Result<PathBuf> {
let file = backup_file(home, cwd);
let dir = file
.parent()
.expect("backup path always ends in .prot under a directory");
std::fs::create_dir_all(dir)
.with_context(|| format!("creating backup directory {}", dir.display()))?;
prot::write(&file, data)?;
Ok(file)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn backup_path_mirrors_the_project_path() {
let home = Path::new("/home/user");
let file = backup_file(home, Path::new("/code/my proj"));
assert_eq!(
file,
Path::new("/home/user/.prot/backups/code/my proj/.prot")
);
}
#[test]
fn distinct_projects_get_distinct_backups() {
let home = Path::new("/home/user");
assert_ne!(
backup_file(home, Path::new("/code/a")),
backup_file(home, Path::new("/code/b"))
);
}
#[test]
fn windows_prefixes_become_plain_names() {
assert_eq!(sanitize_prefix("C:"), "C");
assert_eq!(sanitize_prefix(r"\\?\C:"), "C");
assert_eq!(sanitize_prefix(r"\\server\share"), "server_share");
}
#[test]
fn save_round_trips_through_the_backup() {
let home = tempfile::tempdir().unwrap();
let mut data = ProtData::empty();
data.vault = Some("VAULT".to_string());
data.set_document(".env", "DOC0");
let file = save(home.path(), Path::new("/code/proj"), &data).unwrap();
let restored = prot::read(&file).unwrap().unwrap();
assert_eq!(restored.vault.as_deref(), Some("VAULT"));
assert_eq!(restored.document_id(".env"), Some("DOC0"));
assert_eq!(restored.patterns, data.patterns);
}
}