use std::path::{Path, PathBuf}; use dirs::config_dir; use nucleo::{Config, Matcher, Utf32Str}; use serde::{Deserialize, Serialize}; use crate::identity::Identity; #[derive(Serialize, Deserialize)] pub struct Database { pub keys: Vec } impl Database { pub fn load_or_create(path: &Path) -> anyhow::Result { if !database_exists(path) { let d = Database { keys: vec![] }; write_database(d, path)?; } load_database(path) } pub fn write(self, path: &Path) -> anyhow::Result<()> { write_database(self, path) } pub fn fuzzy_search(&self, term: String) -> Vec { if self.keys.is_empty() { return vec![] } let mut m = Matcher::new(Config::DEFAULT); let haystacks = self.keys.iter().map(|u| { (u.clone(), format!("{:?} {}", u, u.keys.pk().unwrap_or("".to_string()))) }).collect::>(); let mut scores = haystacks.iter().map(|h| { ( m.fuzzy_match(Utf32Str::Ascii(h.1.as_bytes()), Utf32Str::Ascii(term.as_bytes())).unwrap_or(0), h.0.clone() ) }).collect::>(); scores.sort_by_key(|u| u.0); scores.reverse(); let mut best_matches = vec![scores[0].clone()]; for score in &scores[1..] { if score.0 == best_matches[0].0 { best_matches.push(score.clone()); } } best_matches.iter().map(|u| u.1.clone()).collect() } } fn database_exists(path: &Path) -> bool { path.exists() } fn load_database(path: &Path) -> anyhow::Result { let f = std::fs::read_to_string(path)?; let data: Database = toml::from_str(&f)?; Ok(data) } fn write_database(d: Database, path: &Path) -> anyhow::Result<()> { let s = toml::to_string(&d)?; let s_with_warning = format!( "# This file is autogenerated; do not edit! sage v{}\n{}", env!("CARGO_PKG_VERSION"), s ); std::fs::write(path, s_with_warning)?; Ok(()) } pub fn db_default() -> PathBuf { config_dir().unwrap().join("sage.toml") }