197 lines
5.4 KiB
Rust
197 lines
5.4 KiB
Rust
#![allow(dead_code)]
|
|
|
|
use std::collections::HashSet;
|
|
use std::fs::{File, OpenOptions};
|
|
use std::hash::{self, Hash};
|
|
use std::io::Read;
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::LazyLock;
|
|
|
|
use crate::java::{JAVA_EXT_CLASS, JAVA_EXT_SOURCE};
|
|
use crate::{DIR_SRC, DIR_TARGET, F_NEST_LOCK, F_NEST_TOML, PROJECT_ROOT};
|
|
|
|
use anyhow::Context;
|
|
use semver::Version;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
pub static NEST: LazyLock<anyhow::Result<Nest>> =
|
|
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<File> for Nest {
|
|
type Error = anyhow::Error;
|
|
|
|
fn try_from(value: File) -> Result<Self, Self::Error> {
|
|
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<PathBuf> for Nest {
|
|
type Error = anyhow::Error;
|
|
|
|
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
|
|
Nest::try_from(
|
|
OpenOptions::new()
|
|
.read(true)
|
|
.open(value)
|
|
.expect("Failed to load Nest.toml"),
|
|
)
|
|
}
|
|
}
|
|
|
|
// Rename to Prey
|
|
// Mark as deprecated soon.
|
|
#[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 classes: HashSet<Class>,
|
|
}
|
|
|
|
impl NestLock {
|
|
pub fn load() -> anyhow::Result<NestLock> {
|
|
NestLock::try_from(PROJECT_ROOT.join(F_NEST_LOCK.as_path()))
|
|
}
|
|
|
|
/// 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.classes = self
|
|
.classes
|
|
.clone()
|
|
.into_iter()
|
|
.filter_map(|class| {
|
|
// Paths are almost correct, except that the target classes are expecting to path
|
|
// through:
|
|
// target/main/Main.class, not target/Main.class
|
|
// target/test/MainTest.class, not target/MainTest.class
|
|
|
|
if DIR_SRC
|
|
.join(&class.path)
|
|
.with_extension(JAVA_EXT_SOURCE)
|
|
.exists()
|
|
&& DIR_TARGET
|
|
.join(&class.subpath())
|
|
.with_extension(JAVA_EXT_CLASS)
|
|
.is_file()
|
|
{
|
|
return Some(class);
|
|
}
|
|
|
|
None
|
|
})
|
|
.collect();
|
|
}
|
|
}
|
|
|
|
impl TryFrom<File> for NestLock {
|
|
type Error = anyhow::Error;
|
|
|
|
fn try_from(value: File) -> Result<Self, Self::Error> {
|
|
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<PathBuf> for NestLock {
|
|
type Error = anyhow::Error;
|
|
|
|
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
|
|
NestLock::try_from(
|
|
OpenOptions::new()
|
|
.read(true)
|
|
.open(&value)
|
|
.with_context(|| format!("Failed to open {}", value.display()))?,
|
|
)
|
|
.context("")
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
|
pub struct Class {
|
|
path: PathBuf,
|
|
checksum: String,
|
|
}
|
|
|
|
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 {
|
|
// Still doesn't handle inter-dependency checks.
|
|
|
|
DIR_TARGET
|
|
.join(self.subpath())
|
|
.with_extension(JAVA_EXT_CLASS)
|
|
.exists()
|
|
&& sha256::try_digest(DIR_SRC.join(self.path()).with_extension(JAVA_EXT_SOURCE))
|
|
.is_ok_and(|hash| self.checksum == hash)
|
|
}
|
|
|
|
/// Returns the path such that src/ and target/ don't matter.
|
|
/// E.g., `main/Main.java` as_subpath is `Main.java`
|
|
/// allowing it to match with `target/main/Main.class`
|
|
/// because Java diregards the top level subdir in `src/`
|
|
fn subpath(&self) -> PathBuf {
|
|
let mut p = self.path.components();
|
|
p.next(); // Remove the top level dir.
|
|
p.as_path().to_path_buf()
|
|
}
|
|
|
|
pub fn path(&self) -> &Path {
|
|
self.path.as_path()
|
|
}
|
|
}
|
|
|
|
impl Hash for Class {
|
|
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
|
self.path.hash(state);
|
|
}
|
|
}
|
|
|
|
impl TryFrom<PathBuf> for Class {
|
|
type Error = anyhow::Error;
|
|
|
|
fn try_from(mut value: PathBuf) -> Result<Self, Self::Error> {
|
|
if value.is_relative() {
|
|
value = value
|
|
.canonicalize()
|
|
.context("Failed to canonicalize path")?;
|
|
}
|
|
|
|
Ok(Self {
|
|
path: PathBuf::from(
|
|
pathsub::sub_paths(value.as_path(), DIR_SRC.as_path())
|
|
.context("Failed to subtract paths to get class path")?,
|
|
)
|
|
.with_extension(""),
|
|
checksum: sha256::try_digest(&value)?,
|
|
})
|
|
}
|
|
}
|