use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::error::FSError;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SavedSearch {
pub name: String,
pub query: String,
}
#[derive(Debug, Default, Serialize, Deserialize)]
struct SavedSearchFile {
#[serde(default)]
search: Vec<SavedSearch>,
}
fn saved_searches_path(workspace_path: &Path) -> std::path::PathBuf {
workspace_path.join(".kimun").join("saved-searches.toml")
}
pub fn saved_search_name_matches(a: &str, b: &str) -> bool {
a.eq_ignore_ascii_case(b)
}
pub async fn read_saved_searches(workspace_path: &Path) -> Result<Vec<SavedSearch>, FSError> {
let path = saved_searches_path(workspace_path);
match tokio::fs::read_to_string(&path).await {
Ok(body) => {
let parsed: SavedSearchFile =
toml::from_str(&body).map_err(|e| FSError::SerializationError(e.to_string()))?;
Ok(parsed.search)
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(Vec::new()),
Err(e) => Err(FSError::ReadFileError(e)),
}
}
pub async fn write_saved_searches(
workspace_path: &Path,
searches: &[SavedSearch],
) -> Result<(), FSError> {
let path = saved_searches_path(workspace_path);
if let Some(parent) = path.parent() {
tokio::fs::create_dir_all(parent).await?;
}
let file = SavedSearchFile {
search: searches.to_vec(),
};
let body =
toml::to_string_pretty(&file).map_err(|e| FSError::SerializationError(e.to_string()))?;
tokio::fs::write(&path, body).await?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn name_match_is_ascii_case_insensitive() {
assert!(saved_search_name_matches("todo", "TODO"));
assert!(saved_search_name_matches("Todo", "toDo"));
assert!(!saved_search_name_matches("todo", "todos"));
assert!(!saved_search_name_matches("naïve", "NAÏVE"));
assert!(saved_search_name_matches("Naïve", "naïve"));
}
#[tokio::test]
async fn read_missing_file_returns_empty() {
let dir = tempfile::TempDir::new().unwrap();
let got = read_saved_searches(dir.path()).await.unwrap();
assert!(got.is_empty());
}
#[tokio::test]
async fn write_then_read_roundtrips() {
let dir = tempfile::TempDir::new().unwrap();
let searches = vec![
SavedSearch {
name: "todo".into(),
query: "#todo".into(),
},
SavedSearch {
name: "backlinks".into(),
query: ">{note}".into(),
},
];
write_saved_searches(dir.path(), &searches).await.unwrap();
let got = read_saved_searches(dir.path()).await.unwrap();
assert_eq!(got, searches);
}
#[tokio::test]
async fn write_creates_kimun_dir() {
let dir = tempfile::TempDir::new().unwrap();
write_saved_searches(dir.path(), &[]).await.unwrap();
assert!(dir
.path()
.join(".kimun")
.join("saved-searches.toml")
.exists());
}
}