use crate::scanner::OpenLoop;
use anyhow::Result;
use std::path::{Path, PathBuf};
pub struct Cache {
dir: PathBuf,
}
impl Cache {
pub fn new(base: &Path) -> Self {
Self {
dir: base.join("cache"),
}
}
fn path(&self, lp: &OpenLoop) -> PathBuf {
let branch = lp.branch.replace('/', "__");
self.dir
.join(&lp.root_label)
.join(&lp.repo_name)
.join(format!("{branch}@{}.md", lp.head_sha))
}
pub fn get(&self, lp: &OpenLoop) -> Option<String> {
std::fs::read_to_string(self.path(lp)).ok()
}
pub fn put(&self, lp: &OpenLoop, content: &str) -> Result<()> {
let path = self.path(lp);
std::fs::create_dir_all(
path.parent()
.ok_or_else(|| anyhow::anyhow!("cache path has no parent directory"))?,
)?;
std::fs::write(path, content)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scanner::OpenLoop;
use chrono::Utc;
use std::path::PathBuf;
fn fake_loop(sha: &str) -> OpenLoop {
OpenLoop {
root_label: "work".into(),
repo_name: "app".into(),
repo_path: PathBuf::from("/tmp/app"),
branch: "feat/login".into(),
head_sha: sha.into(),
last_commit: Utc::now(),
ahead: Some(1),
behind: Some(0),
}
}
#[test]
fn miss_then_put_then_hit() {
let tmp = tempfile::tempdir().unwrap();
let cache = Cache::new(tmp.path());
let lp = fake_loop("abc123");
assert!(cache.get(&lp).is_none());
cache.put(&lp, "distilled context").unwrap();
assert_eq!(cache.get(&lp).unwrap(), "distilled context");
}
#[test]
fn new_head_self_invalidates() {
let tmp = tempfile::tempdir().unwrap();
let cache = Cache::new(tmp.path());
cache.put(&fake_loop("old-sha"), "old").unwrap();
assert!(cache.get(&fake_loop("new-sha")).is_none());
}
#[test]
fn path_includes_root_label_segment() {
let tmp = tempfile::tempdir().unwrap();
let cache = Cache::new(tmp.path());
let lp = fake_loop("sha1");
cache.put(&lp, "x").unwrap();
let mut other = fake_loop("sha1");
other.root_label = "personal".into();
assert!(cache.get(&other).is_none());
}
}