use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::path::Path;
pub const LOCK_FILE: &str = ".repoverse.lock";
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct Lock {
pub version: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub generated: Option<String>,
#[serde(default)]
pub projects: Vec<LockedProject>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct LockedProject {
pub path: String,
pub name: String,
pub revision: String,
pub sha: String,
}
impl Lock {
pub fn new() -> Lock {
Lock {
version: 1,
generated: None,
projects: Vec::new(),
}
}
pub fn load(path: &Path) -> Result<Lock> {
let text =
std::fs::read_to_string(path).with_context(|| format!("reading {}", path.display()))?;
let lock: Lock =
serde_yaml::from_str(&text).with_context(|| format!("parsing {}", path.display()))?;
Ok(lock)
}
pub fn load_or_default(path: &Path) -> Result<Lock> {
if path.is_file() {
Lock::load(path)
} else {
Ok(Lock::new())
}
}
pub fn save(&self, path: &Path) -> Result<()> {
let text = serde_yaml::to_string(self).context("serializing lock")?;
std::fs::write(path, text).with_context(|| format!("writing {}", path.display()))?;
Ok(())
}
#[allow(dead_code)]
pub fn by_path(&self) -> BTreeMap<&str, &LockedProject> {
self.projects.iter().map(|p| (p.path.as_str(), p)).collect()
}
pub fn sha_for(&self, path: &str) -> Option<&str> {
self.projects
.iter()
.find(|p| p.path == path)
.map(|p| p.sha.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn roundtrip() {
let dir = tempdir().unwrap();
let path = dir.path().join(LOCK_FILE);
let mut lock = Lock::new();
lock.projects.push(LockedProject {
path: "lib".into(),
name: "acme/lib".into(),
revision: "main".into(),
sha: "abc123".into(),
});
lock.save(&path).unwrap();
let loaded = Lock::load(&path).unwrap();
assert_eq!(lock, loaded);
assert_eq!(loaded.sha_for("lib"), Some("abc123"));
}
#[test]
fn missing_file_defaults() {
let dir = tempdir().unwrap();
let lock = Lock::load_or_default(&dir.path().join(LOCK_FILE)).unwrap();
assert!(lock.projects.is_empty());
}
}