Something went wrong. Try again.
Current idea for autocomplete cache
Something went wrong. Try again.
11 kB · 312 lines · Created 5mo ago
Rust
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313use serde::{Deserialize, Serialize};use std::{collections::BTreeMap, fmt::Display, path::PathBuf};use tracing::{debug, instrument, trace};
pub const NILLA_PROJECT_FORMAT_VERSION: u32 = 0;
#[derive(Debug, Clone, Serialize, Deserialize)]pub struct Project { pub version: u32, pub name: String, pub languages: Option<Vec<String>>, pub packages: Option<Vec<String>>, pub shells: Option<Vec<String>>, pub extensions_dict: Option<BTreeMap<String, serde_json::Value>>,}
#[derive(Debug)]pub enum CacheError { IoError(std::io::Error), ParseError(serde_json::Error), VersionMismatch(u32), UpdateRequired,}
impl From<std::io::Error> for CacheError { fn from(e: std::io::Error) -> Self { CacheError::IoError(e) }}
impl From<serde_json::Error> for CacheError { fn from(e: serde_json::Error) -> Self { CacheError::ParseError(e) }}
impl Display for CacheError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { CacheError::IoError(e) => write!(f, "IO error: {}", e), CacheError::ParseError(e) => write!(f, "Parse error: {}", e), CacheError::VersionMismatch(v) => { write!( f, "Format version mismatch. Update the project dir. Expected version {}, found {}", NILLA_PROJECT_FORMAT_VERSION, v ) } CacheError::UpdateRequired => write!(f, "Project dir out of date. Update required"), } }}
impl Project { pub fn new(name: String) -> Self { Self { version: NILLA_PROJECT_FORMAT_VERSION, name, languages: Some(Vec::new()), packages: Some(Vec::new()), shells: Some(Vec::new()), extensions_dict: None, } }
#[instrument(name = "load project")] pub fn load(path: &PathBuf) -> Result<Self, CacheError> { let nilla_project_dir = path.join(".nilla"); let nilla_project_dir_meta = std::fs::metadata(&nilla_project_dir)?; let nilla_nix_meta = std::fs::metadata(&path.join("nilla.nix"))?;
trace!( "comparing nilla.nix modified time ({:?}) to nilla project dir modified time ({:?}), to ensure project dir is up to date", nilla_nix_meta.modified()?, nilla_project_dir_meta.modified()? ); if nilla_nix_meta.modified()? > nilla_project_dir_meta.modified()? { return Err(CacheError::UpdateRequired); }
debug!("loading project from nilla project dir"); let nilla_project_file = std::fs::File::open(&nilla_project_dir.join("project.json"))?;
debug!("parsing project from nilla project dir"); let mut rdr = std::io::BufReader::new(nilla_project_file); let mut nilla_project: Project = serde_json::from_reader(&mut rdr)?;
let files = nilla_project_dir.read_dir()?; let mut extensions_dict = BTreeMap::new(); for file in files { let file = file?; let file_name = file.file_name().to_string_lossy().into_owned(); if file_name == "project.json" { continue; } if file_name.ends_with(".json") { let extension_name = file_name.trim_end_matches(".json"); let extension_file = std::fs::File::open(file.path())?; let extension: serde_json::Value = serde_json::from_reader(extension_file)?; extensions_dict.insert(extension_name.to_lowercase(), extension); } } nilla_project.extensions_dict = Some(extensions_dict);
if nilla_project.version != NILLA_PROJECT_FORMAT_VERSION { return Err(CacheError::VersionMismatch(nilla_project.version)); } debug!("project loaded successfully");
Ok(nilla_project) }
fn base(self) -> Self { Self { version: self.version, name: self.name.clone(), languages: self.languages.clone(), packages: self.packages.clone(), shells: self.shells.clone(), extensions_dict: None, } }
#[instrument(name = "save project")] pub fn save(self, path: &PathBuf) -> Result<(), CacheError> { let nilla_project_dir = path.join(".nilla"); std::fs::create_dir_all(&nilla_project_dir)?; debug!("saving project to {}", nilla_project_dir.display()); if let Some(extensions_dict) = &self.extensions_dict { for (name, extension) in extensions_dict { let extension_file = std::fs::File::create(&nilla_project_dir.join(format!("{}.json", name)))?; serde_json::to_writer(extension_file, extension)?; } } let nilla_project_file = std::fs::File::create(&nilla_project_dir.join("project.json"))?; serde_json::to_writer(nilla_project_file, &self.base())?; debug!("project saved successfully"); Ok(()) }}
#[cfg(test)]mod tests { use super::*;
#[test] fn save_project() { let temp_project_dir = tempfile::tempdir().unwrap(); std::fs::File::create(&temp_project_dir.path().join("nilla.nix")).unwrap();
let project = Project::new("test".to_string()); project .save(&PathBuf::from(temp_project_dir.path())) .unwrap(); assert!( temp_project_dir .path() .join(".nilla") .join("project.json") .exists() ); temp_project_dir.close().unwrap(); }
#[test] fn load_project() { let temp_project_dir = tempfile::tempdir().unwrap(); std::fs::File::create(&temp_project_dir.path().join("nilla.nix")).unwrap();
let mut project = Project::new("test".to_string()); project.languages.as_mut().unwrap().push("rust".to_string()); project .shells .as_mut() .unwrap() .append(&mut vec!["default".to_string()]); project .packages .as_mut() .unwrap() .append(&mut vec!["default".to_string()]); project .save(&PathBuf::from(temp_project_dir.path())) .unwrap();
let loaded_project = Project::load(&temp_project_dir.path().into()).unwrap(); assert_eq!(loaded_project.name, "test"); assert_eq!(loaded_project.languages, Some(vec!["rust".to_string()])); assert_eq!(loaded_project.shells, Some(vec!["default".to_string()])); assert_eq!(loaded_project.packages, Some(vec!["default".to_string()])); temp_project_dir.close().unwrap(); }
#[test] fn extensions_test() { let temp_project_dir = tempfile::tempdir().unwrap(); std::fs::File::create(&temp_project_dir.path().join("nilla.nix")).unwrap();
let mut project = Project::new("test".to_string()); assert!(project.extensions_dict.is_none()); project.extensions_dict = Some(BTreeMap::new()); let test_extension = serde_json::json!({ "systems": vec!["default".to_string()] }); project .extensions_dict .as_mut() .unwrap() .insert("test".to_string(), test_extension); assert!(project.extensions_dict.is_some());
project .save(&PathBuf::from(temp_project_dir.path())) .unwrap();
assert!( temp_project_dir .path() .join(".nilla") .join("test.json") .exists() ); let loaded_project = Project::load(&temp_project_dir.path().into()).unwrap(); assert!(loaded_project.extensions_dict.is_some()); assert!( loaded_project .extensions_dict .as_ref() .unwrap() .contains_key("test") ); assert_eq!( loaded_project .extensions_dict .as_ref() .unwrap() .get("test") .unwrap(), &serde_json::json!({ "systems": vec!["default".to_string()] }) ); temp_project_dir.close().unwrap(); }
#[test] fn needs_update() { let temp_project_dir = tempfile::tempdir().unwrap(); let mut project = Project::new("test".to_string()); project.languages.as_mut().unwrap().push("rust".to_string()); project .shells .as_mut() .unwrap() .append(&mut vec!["default".to_string()]); project .packages .as_mut() .unwrap() .append(&mut vec!["default".to_string()]); project .save(&PathBuf::from(temp_project_dir.path())) .unwrap();
std::thread::sleep(std::time::Duration::from_millis(1));
std::fs::File::create(&temp_project_dir.path().join("nilla.nix")).unwrap();
let loaded_project = Project::load(&temp_project_dir.path().into());
assert!(loaded_project.is_err()); assert_eq!( loaded_project.unwrap_err().to_string(), "Project dir out of date. Update required" );
temp_project_dir.close().unwrap(); }
#[test] fn version_mismatch() { let temp_project_dir = tempfile::tempdir().unwrap(); let json = serde_json::json!({ "version": u32::MAX, "name": "test", "languages": vec!["rust".to_string()], "shells": vec!["default".to_string()], "packages": vec!["default".to_string()], });
std::fs::File::create(&temp_project_dir.path().join("nilla.nix")).unwrap(); std::fs::create_dir(&temp_project_dir.path().join(".nilla")).unwrap(); let f = std::fs::File::create(&temp_project_dir.path().join(".nilla").join("project.json")) .unwrap(); serde_json::to_writer(f, &json).unwrap();
let loaded_project = Project::load(&temp_project_dir.path().into());
assert!(loaded_project.is_err()); assert_eq!( loaded_project.unwrap_err().to_string(), format!( "Format version mismatch. Update the project dir. Expected version {}, found {}", NILLA_PROJECT_FORMAT_VERSION, u32::MAX ) );
temp_project_dir.close().unwrap(); }}