use std::collections::HashMap; use std::fs::{File, OpenOptions}; use std::io::Read; use std::path::PathBuf; use std::sync::LazyLock; use crate::java::{JAVA_EXT_CLASS, JAVA_EXT_SOURCE}; use crate::{DIR_SRC, DIR_TARGET, DIR_TEST, F_NEST_LOCK, F_NEST_TOML, PROJECT_ROOT}; use anyhow::Context; use semver::Version; use serde::{Deserialize, Serialize}; pub static NEST: LazyLock> = LazyLock::new(|| Nest::try_from(PROJECT_ROOT.join(F_NEST_TOML.as_path()))); #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Nest { pub package: Package, } impl TryFrom for Nest { type Error = anyhow::Error; fn try_from(value: File) -> Result { let mut value = value; let mut buf = String::new(); value .read_to_string(&mut buf) .context("Failed to read Nest")?; toml::from_str(buf.as_str()).context("Failed to deserialize Nest") } } impl TryFrom for Nest { type Error = anyhow::Error; fn try_from(value: PathBuf) -> Result { Nest::try_from( OpenOptions::new() .read(true) .open(value) .expect("Failed to load Nest.toml"), ) } } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Package { pub name: String, pub version: semver::Version, } impl Default for Package { fn default() -> Self { Self { name: String::from("MyPackage"), version: Version::new(0, 1, 0), } } } #[derive(Debug, Default, Deserialize, Serialize)] pub struct NestLock(pub HashMap); impl NestLock { pub fn load() -> anyhow::Result { NestLock::try_from(F_NEST_LOCK.clone()) } /// Update, retaining all classes that still exist and whose paths are still files, rather than /// being shifted into a package. pub fn update(&mut self) { self.0 = self .0 .clone() .into_iter() .filter_map(|(path, class)| { if path.exists() && path.is_file() { return Some((path, class)); } None }) .collect(); } } impl TryFrom for NestLock { type Error = anyhow::Error; fn try_from(value: File) -> Result { let mut value = value; let mut buf = String::new(); value.read_to_string(&mut buf)?; toml::from_str(buf.as_str()).context("Failed to deserialize NestLock") } } impl TryFrom for NestLock { type Error = anyhow::Error; fn try_from(value: PathBuf) -> Result { NestLock::try_from( OpenOptions::new() .read(true) .open(&value) .with_context(|| format!("Failed to open {}", value.display()))?, ) .context("") } } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Class { pub name: PathBuf, pub checksum: String, pub test: bool, } impl Class { /// Returns true if the class needs updating. /// This may also cautionarily return true if it cannot digest the file. pub fn is_updated(&self) -> bool { // If the source file exists, hasn't been moved to a package (path is a file) and // the associated compiled class file exists in DIR_TARGET PROJECT_ROOT .join(DIR_TARGET.as_path()) .join(self.name.as_path()) .with_extension(JAVA_EXT_CLASS) .exists() && sha256::try_digest( PROJECT_ROOT .join(if self.test { DIR_TEST.clone() } else { DIR_SRC.clone() }) .join(self.name.as_path()) .with_extension(JAVA_EXT_SOURCE), ) .is_ok_and(|hash| self.checksum == hash) } } impl TryFrom for Class { type Error = anyhow::Error; fn try_from(value: PathBuf) -> Result { Ok(Self { name: PathBuf::from( value .file_name() .context("Failed to get file name from PathBuf for class")?, ) .with_extension(""), checksum: sha256::try_digest(&value)?, test: value.is_relative() && value.starts_with(DIR_TEST.as_path()), }) } }