use std::collections::HashMap; use std::fs::{OpenOptions, read_dir}; use std::io::{Read, Write}; use std::path::{Path, PathBuf}; use std::time::Duration; use fs::expand_files; use java::{JAVA_EXT_CLASS, JAVA_EXT_SOURCE}; use serde::{Deserialize, Serialize}; use subprocess::Exec; use crate::nest::{F_NEST_LOCK, F_NEST_TOML, Nest, NestLock}; use crate::package::PackageHandler; use crate::{Error, package}; #[derive(Debug)] pub struct WorkspaceHandler { nest: Nest, nest_lock: Option, project_root: PathBuf, packages: HashMap, } impl WorkspaceHandler { const DIR_SRC: &'static str = "src/"; const DIR_TARGET: &'static str = "target/"; pub fn new>(project_root: P) -> crate::Result { let project_root = project_root.as_ref().canonicalize()?; Ok(Self { nest: Nest::new( project_root .file_name() .ok_or(Error::MissingFileName)? .display(), ), nest_lock: None, packages: HashMap::new(), project_root, }) } pub fn load>(project_root: P) -> crate::Result { let project_root = project_root.as_ref().canonicalize()?; println!("{}", project_root.display()); let mut workspace_manager = Self { nest: Nest::try_from(project_root.join(F_NEST_TOML))?, nest_lock: NestLock::try_from(project_root.join(F_NEST_LOCK)).ok(), packages: HashMap::new(), project_root, }; workspace_manager.discover_packages()?; Ok(workspace_manager) } pub fn write_locks(&self) -> crate::Result<()> { if let Option::Some(lock) = self.nest_lock.clone() { lock.write(self.project_root.join(F_NEST_LOCK))?; } for handler in self.packages.values() { handler.write_lock()?; } Ok(()) } pub fn init(&mut self) -> crate::Result<&mut Self> { // ORDER MATTERS. let is_empty = read_dir(self.project_root.as_path()).is_ok_and(|tree| tree.count() == 0); // Make config file. self.write_nest()?; // Make .java-version self.write_java_version()?; if is_empty { self.write_example_project()?; self.discover_packages()?; Exec::cmd("git") .arg("init") .arg(".") .start()? .wait_timeout(Duration::from_secs(10))?; } // Append to .gitignore if let Result::Ok(mut f) = OpenOptions::new() .append(true) .create(true) .read(true) .open(".gitignore") { let mut buf = String::new(); f.read_to_string(&mut buf)?; for ignored in [ "# Automatically added by Raven".to_string(), Self::DIR_TARGET.to_string(), format!("*.{}", JAVA_EXT_CLASS), ] { if !buf.contains(&ignored) { f.write(format!("{}\n", ignored).as_bytes())?; } } } Ok(self) } // This is the naive build pub fn build(&mut self) -> crate::Result<&mut Self> { let compiler = java::compiler::CompilerBuilder::new() .class_path(Self::DIR_TARGET) .destination(Self::DIR_TARGET) .build(); // cyclic dependencies across packages are not supported. // construct a hashmap of parent child relationships between all files across all packages. for handler in self.packages.values_mut() { let package_src_root = self .project_root .join(Self::DIR_SRC) .join(handler.name()) .join(package::DIR_JAVA); let targets = handler.get_outdated(self.project_root.join(Self::DIR_TARGET)); for mut target in targets { dbg!(&target); if let Ok(true) = compiler .clone() .compile(vec![package_src_root.join(target.path.as_path())]) { target.update(package_src_root.as_path())?; handler .prey_lock() .classes() .replace(target) .ok_or(Error::AbsentPrimeHashingError)?; } } } self.write_locks()?; Ok(self) } pub fn run>( &mut self, entry_point: Option

, assertions: bool, ) -> crate::Result<&mut Self> { let mut entry_point = entry_point.expect("No entry point found").as_ref().to_path_buf(); while entry_point.is_file() { entry_point = self .packages .get(&entry_point) .ok_or(Error::UnknownPackage)? .entry_point() .expect("No entry point found"); } // let mut entry_point = if entry_point.is_none() { // self.nest.entry_point() // } else { // //entry_point.unwrap().as_ref().to_path_buf() // }; // // if !entry_point.is_file() { // // Use is_file to skip messing with pathing for src/ // // If target is not a file (explicit entry point), check if it's a known package // // and use that's package's default entry point. // entry_point = self // .packages // .get(&entry_point) // .ok_or(Error::UnknownPackage)? // .entry_point(); // } // if entry_point.file_name().is_none() { return Err(Error::UndefinedEntryPoint); } // java::runtime::JVMBuilder::new(Self::DIR_TARGET) .assertions(assertions) .monitor(true) .build() .run(entry_point)?; Ok(self) } pub fn clean(&mut self) -> crate::Result<&mut Self> { // Clear Nest.lock if let Err(err) = std::fs::remove_file(self.project_root.join(F_NEST_LOCK)) { if err.kind() != std::io::ErrorKind::NotFound { return Err(Error::from(err)); } } // Clear src/**/Prey.lock for handler in self.packages.values() { if let Err(err) = std::fs::remove_file( self.project_root .join(Self::DIR_SRC) .join(handler.name()) .join(F_NEST_LOCK), ) { if err.kind() != std::io::ErrorKind::NotFound { return Err(Error::from(err)); } } } // Clear target/ let _ = std::fs::remove_dir_all(Self::DIR_TARGET); Ok(self) } /// Add any newly created packages. /// Prey.toml files designate packages. /// discover_packages() iterates the filesystem recursively to discover all such packages. /// discovered packages are stored as a hashmap in the WorkspaceHandler. fn discover_packages(&mut self) -> crate::Result<()> { // Scan the src/ directory for entries, // filter out the files, // then construct PackageManagers for each package // Promote *not* using reverse domain name tree structures // by improving the speed of package discovery by using read_dir // and checking for an immediate Prey.toml before expanding the // whole subtree. // // Yes, I know this looks like shit. // That's because it is. CON-FUCKING-FIRMED THIS SHIT IS NOT DEBUGGABLE for file in read_dir(self.project_root.join(Self::DIR_SRC))? // Get directories .filter_map(|entry| { if entry.as_ref().is_ok_and(|entry| entry.path().is_dir()) { Some(entry.unwrap().path()) } else { None } }) // Get Prey.toml files .filter_map(|dir| { Some(if dir.join(F_NEST_TOML).exists() { vec![dir.join(F_NEST_TOML)] } else { expand_files(dir) .ok()? .iter() .filter_map(|file| { if file.ends_with(PathBuf::from(F_NEST_TOML)) { Some(file.to_owned()) } else { None } }) .collect() }) }) .flatten() { let package_root = pathsub::sub_paths( file.as_path(), self.project_root.join(Self::DIR_SRC).as_path(), ) .unwrap() .parent() .unwrap() .to_path_buf(); self.packages.insert( package_root.to_path_buf(), PackageHandler::new( self.project_root.join(Self::DIR_SRC), package_root, self.project_root.join(Self::DIR_TARGET), )?, ); } Ok(()) } fn write_nest(&self) -> crate::Result<()> { Ok(self.nest.write(self.project_root.clone())?) } fn write_java_version(&self) -> crate::Result<()> { if let Result::Ok(mut f) = OpenOptions::new() .write(true) .create_new(true) .open(java::F_JAVA_VERSION) { f.write_all(format!("{}\n", java::get_javac_version()?.major.to_string()).as_bytes())?; } Ok(()) } fn write_dir_tree(&self) -> std::io::Result<()> { for dir in [ format!("{}main/java", Self::DIR_SRC), format!("{}test/java", Self::DIR_SRC), Self::DIR_TARGET.to_string(), ] { std::fs::create_dir_all(std::env::current_dir()?.join(dir))?; } Ok(()) } fn write_example_project(&self) -> crate::Result<()> { let main: PathBuf = PathBuf::from(Self::DIR_SRC).join("main/"); let test: PathBuf = PathBuf::from(Self::DIR_SRC).join("test/"); // Make src/, target/, test/ self.write_dir_tree()?; // Make src/main/Prey.toml if let Result::Ok(mut f) = OpenOptions::new() .write(true) .create_new(true) .open(main.join(F_NEST_TOML)) { f.write_all( toml::to_string_pretty(&Nest::new("main").with_entry_point("Main"))?.as_bytes(), )?; } // Make src/main/Main.java if let Result::Ok(mut f) = OpenOptions::new() .write(true) .create_new(true) .open(main.join("java/Main").with_extension(JAVA_EXT_SOURCE)) { f.write_all(include_bytes!("../assets/Main.java"))?; } // Make src/test/Prey.toml if let Result::Ok(mut f) = OpenOptions::new() .write(true) .create_new(true) .open(test.join(F_NEST_TOML)) { f.write_all( toml::to_string_pretty(&Nest::new("test").with_entry_point("MainTest"))?.as_bytes(), )?; } // Make src/test/MainTest.java if let Result::Ok(mut f) = OpenOptions::new() .write(true) .create_new(true) .open(test.join("java/MainTest").with_extension(JAVA_EXT_SOURCE)) { f.write_all(include_bytes!("../assets/MainTest.java"))?; } Ok(()) } } /// Data struct #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Workspace { pub default_package: PathBuf, } impl Default for Workspace { fn default() -> Self { Workspace { default_package: PathBuf::from("main"), } } }