Files
zesdex/src/model/editlog.rs
T

122 lines
3.8 KiB
Rust
Raw Normal View History

use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EditLogEntry {
pub ts: i64,
pub tool: String,
pub path: String,
pub reason: String,
pub content_sha256: String,
pub bytes_delta: i64,
pub origin: String,
pub session_id: String,
}
#[derive(Debug, Clone)]
pub struct EditLog {
pub entries: Vec<EditLogEntry>,
pub path: std::path::PathBuf,
}
impl EditLog {
pub fn new(session_dir: &std::path::Path) -> Self {
let path = session_dir.join("edits.jsonl");
let entries = Self::load_from_disk(&path);
EditLog { entries, path }
}
/// Reads every line of edits.jsonl back into memory so callers who create a
/// *new* EditLog after a previous session can inspect the full history.
fn load_from_disk(path: &std::path::Path) -> Vec<EditLogEntry> {
let file = match std::fs::File::open(path) {
Ok(f) => f,
Err(_) => return Vec::new(),
};
use std::io::{BufRead, BufReader};
let reader = BufReader::new(file);
reader
.lines()
.filter_map(|line| line.ok().and_then(|l| serde_json::from_str(&l).ok()))
.collect()
}
pub fn append(&mut self, entry: EditLogEntry) -> std::io::Result<()> {
let line = serde_json::to_string(&entry)? + "\n";
let parent = self.path.parent().unwrap();
std::fs::create_dir_all(parent)?;
let mut file = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&self.path)?;
use std::io::Write;
file.write_all(line.as_bytes())?;
self.entries.push(entry);
Ok(())
}
pub fn len(&self) -> usize {
self.entries.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_editlog_new_empty() {
let dir = std::env::temp_dir().join("editlog_test");
let _ = std::fs::create_dir_all(&dir);
let log = EditLog::new(&dir);
assert_eq!(log.len(), 0);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn test_editlog_append_and_reload() {
let dir = std::env::temp_dir().join("editlog_append_test");
let _ = std::fs::create_dir_all(&dir);
let mut log = EditLog::new(&dir);
let entry = EditLogEntry {
ts: 1,
tool: "write".to_string(),
path: "test.txt".to_string(),
reason: "test reason".to_string(),
content_sha256: "abc123".to_string(),
bytes_delta: 42,
origin: "main".to_string(),
session_id: "sess-1".to_string(),
};
log.append(entry.clone()).unwrap();
assert_eq!(log.len(), 1);
assert_eq!(log.entries[0].reason, "test reason");
assert_eq!(log.entries[0].tool, "write");
assert_eq!(log.entries[0].path, "test.txt");
assert_eq!(log.entries[0].bytes_delta, 42);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn test_editlog_multiple_entries() {
let dir = std::env::temp_dir().join("editlog_multiple_test");
let _ = std::fs::create_dir_all(&dir);
let mut log = EditLog::new(&dir);
for i in 0..5 {
log.append(EditLogEntry {
ts: i,
tool: "edit".to_string(),
path: format!("file{}.txt", i),
reason: format!("reason {}", i),
content_sha256: "hash".to_string(),
bytes_delta: 10 + i,
origin: "main".to_string(),
session_id: "sess-1".to_string(),
}).unwrap();
}
assert_eq!(log.len(), 5);
assert_eq!(log.entries[0].reason, "reason 0");
assert_eq!(log.entries[4].reason, "reason 4");
let _ = std::fs::remove_dir_all(&dir);
}
}