use serde::{Deserialize, Serialize};
use std::fs::{File, OpenOptions};
use std::io::{BufRead, BufReader, Write};
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Choice {
Keep,
Drop,
Skip,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DecisionRecord {
pub group_id: String,
pub phenom_id: String,
pub choice: Choice,
pub chart_name: String,
}
#[derive(Debug, thiserror::Error)]
pub enum DecisionLogError {
#[error("decision log I/O: {0}")]
Io(#[from] std::io::Error),
#[error("decision log JSON: {0}")]
Json(#[from] serde_json::Error),
}
pub struct DecisionLog {
file: File,
path: PathBuf,
}
impl DecisionLog {
pub fn open(path: &Path) -> Result<Self, DecisionLogError> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let file = OpenOptions::new().create(true).append(true).open(path)?;
Ok(Self {
file,
path: path.to_path_buf(),
})
}
pub fn append(&mut self, rec: &DecisionRecord) -> Result<(), DecisionLogError> {
let line = serde_json::to_string(rec)?;
writeln!(self.file, "{line}")?;
self.file.sync_data()?;
Ok(())
}
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
pub fn read_all(path: &Path) -> Result<Vec<DecisionRecord>, DecisionLogError> {
if !path.exists() {
return Ok(Vec::new());
}
let file = File::open(path)?;
let reader = BufReader::new(file);
let mut out = Vec::new();
for line in reader.lines() {
let line = line?;
if line.is_empty() {
continue;
}
if let Ok(rec) = serde_json::from_str::<DecisionRecord>(&line) {
out.push(rec);
}
}
Ok(out)
}
}