Files
openworm/src/rsc.rs
2026-02-17 17:39:42 -05:00

104 lines
2.9 KiB
Rust

use std::{
fs::{self, File},
io::Write,
path::PathBuf,
};
use directories_next::ProjectDirs;
#[derive(Clone)]
pub struct DataDir {
pub path: PathBuf,
}
impl DataDir {
pub fn new(dir: Option<&str>) -> Self {
let dirs = ProjectDirs::from("", "", "openworm").unwrap();
let mut path = dirs.data_local_dir().to_path_buf();
if let Some(dir) = dir {
path = path.join(dir);
}
Self { path }
}
}
pub trait DataRsc: serde::Serialize + Default {
fn name() -> &'static str;
fn path() -> String {
Self::name().to_string() + ".ron"
}
fn parse_version(text: &str, version: u32) -> Result<Self, String>;
fn version() -> u32;
}
pub struct DataGuard<T: DataRsc> {
path: PathBuf,
val: T,
}
impl DataDir {
pub fn load<T: DataRsc>(&self) -> DataGuard<T> {
let path = self.path.join(T::path());
let invalid = |info: &str| {
println!("warning: invalid config @ {path:?}: {info}");
DataGuard {
path: path.clone(),
val: T::default(),
}
};
match fs::read_to_string(&path) {
Ok(text) => {
let mut lines = text.lines();
let Some(first) = lines.next() else {
return invalid("empty file");
};
let version_str: String = first
.chars()
.skip_while(|c| *c != 'v')
.skip(1)
.take_while(|c| !c.is_whitespace())
.collect();
let Ok(version): Result<u32, _> = version_str.parse() else {
return invalid("invalid version");
};
let text: String = lines.collect();
match T::parse_version(&text, version) {
Ok(val) => DataGuard { path, val },
Err(e) => invalid(&e),
}
}
Err(_) => DataGuard {
path,
val: T::default(),
},
}
}
}
impl<T: DataRsc> std::ops::Deref for DataGuard<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.val
}
}
impl<T: DataRsc> std::ops::DerefMut for DataGuard<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.val
}
}
impl<T: DataRsc> Drop for DataGuard<T> {
fn drop(&mut self) {
let dir = self.path.parent().unwrap();
fs::create_dir_all(dir).unwrap();
let mut file = File::create(dir.join(T::path())).unwrap();
let ron = ron::ser::to_string_pretty(&self.val, ron::ser::PrettyConfig::new()).unwrap();
let data = format!("// v{}\n{}\n", T::version(), ron);
if let Err(e) = file.write_all(data.as_bytes()) {
println!("Failed to write config @ {:?}: {e}", self.path);
}
}
}