use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct SortMemory {
pub kinds: BTreeMap<String, String>,
}
impl SortMemory {
pub fn default_path() -> PathBuf {
crate::diagnostics::state_dir().join("sort.toml")
}
pub fn load(path: &Path) -> Self {
std::fs::read_to_string(path)
.ok()
.and_then(|s| toml::from_str(&s).ok())
.unwrap_or_default()
}
pub fn save(&self, path: &Path) -> Result<(), String> {
if let Some(dir) = path.parent() {
std::fs::create_dir_all(dir).map_err(|e| e.to_string())?;
}
let text = toml::to_string(self).map_err(|e| e.to_string())?;
std::fs::write(path, text).map_err(|e| e.to_string())
}
pub fn set(&mut self, kind: &str, header: &str, desc: bool) {
let spec = if desc {
format!("{header}:desc")
} else {
header.to_string()
};
self.kinds.insert(kind.to_string(), spec);
}
pub fn clear(&mut self, kind: &str) -> bool {
self.kinds.remove(kind).is_some()
}
pub fn get(&self, kind: &str) -> Option<(String, bool)> {
let spec = self.kinds.get(kind)?;
let (name, desc) = match spec.rsplit_once(':') {
Some((col, "desc")) => (col.trim(), true),
Some((col, "asc")) => (col.trim(), false),
_ => (spec.trim(), false),
};
Some((name.to_uppercase(), desc))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn set_get_roundtrip_and_clear() {
let mut m = SortMemory::default();
m.set("pods", "RESTARTS", true);
m.set("deployments", "READY", false);
assert_eq!(m.get("pods"), Some(("RESTARTS".into(), true)));
assert_eq!(m.get("deployments"), Some(("READY".into(), false)));
assert_eq!(m.get("nodes"), None);
assert!(m.clear("pods"));
assert!(!m.clear("pods"));
assert_eq!(m.get("pods"), None);
}
#[test]
fn get_parses_hand_edited_specs() {
let mut m = SortMemory::default();
m.kinds.insert("pods".into(), "age:desc".into());
m.kinds.insert("nodes".into(), "cpu:asc".into());
m.kinds.insert("jobs".into(), "completions".into());
assert_eq!(m.get("pods"), Some(("AGE".into(), true)));
assert_eq!(m.get("nodes"), Some(("CPU".into(), false)));
assert_eq!(m.get("jobs"), Some(("COMPLETIONS".into(), false)));
}
#[test]
fn save_load_roundtrip() {
let dir = std::env::temp_dir().join(format!("sofka-sortmem-{}", std::process::id()));
let path = dir.join("sort.toml");
let mut m = SortMemory::default();
m.set("pods", "RESTARTS", true);
m.save(&path).unwrap();
assert_eq!(SortMemory::load(&path), m);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn load_missing_or_garbage_is_empty() {
assert_eq!(
SortMemory::load(Path::new("/nonexistent/sort.toml")),
SortMemory::default()
);
}
}