use std::collections::HashMap; use std::fs::{OpenOptions, read_dir}; use std::io::{Read, Write}; use std::path::{Path, PathBuf}; use fs::expand_files; use io::run_process; use java::{JAVA_EXT_CLASS, JAVA_EXT_SOURCE}; use serde::{Deserialize, Serialize}; use crate::Error; use crate::nest::{F_NEST_LOCK, F_NEST_TOML, Nest, NestLock}; use crate::package::PackageHandler; use crate::prey::F_PREY_TOML; pub struct WorkspaceHandler { nest: Nest, nest_lock: Option, project_root: PathBuf, packages: HashMap, } impl WorkspaceHandler { const DIR_SRC: &str = "src/"; const DIR_TARGET: &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()?; 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(&self) -> crate::Result<()> { self.nest.write(self.project_root.join(F_NEST_TOML))?; if let Option::Some(lock) = self.nest_lock.clone() { lock.write(self.project_root.join(F_NEST_LOCK))?; } Ok(()) } //pub fn refresh_packages(&mut self) -> crate::Result<()> {} /* /// Future `build` method. pub fn compile(&self, target: Option) -> crate::Result<()> { let mut target = target.unwrap_or(self.nest.default_package()); if !target.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. target = target.join( self.packages .get(&target) .ok_or(Error::UnknownPackage)? .entry_point(), ); } //java::Compiler::new(); Ok(()) } fn make_compiler_job>(target: P) { // Generate dependency tree. } */ pub fn init(&mut self) -> crate::Result<()> { let is_empty = read_dir(self.project_root.as_path()).is_ok_and(|tree| tree.count() == 0); // ORDER MATTERS. THIS MUST COME FIRST. // Make config file. self.write_nest()?; // Make .java-version self.write_java_version()?; // Make src/, target/, test/ self.write_dir_tree()?; if !is_empty { self.write_example_project()?; self.discover_packages()?; run_process(&["git", "init", "."])?; } // 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(()) } // This is the naive build pub fn build(&mut self) -> crate::Result<()> { let mut targets = vec![]; for (_name, mut handler) in self.packages.clone() { targets.append(&mut handler.get_update_targets()?); } let compiler = java::compiler::CompilerBuilder::new() .class_path(Self::DIR_TARGET) .class_path(Self::DIR_TARGET) .build(); for target in targets { // Possibly come up with a source file handler for this? if let Ok(_) = compiler.clone().compile(target) { // No, this does not run O(1) } } Ok(()) } /// Add any newly created packages. 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. for file in read_dir(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_PREY_TOML).exists() { vec![dir.join(F_PREY_TOML)] } else { expand_files(dir) .ok()? .iter() .filter_map(|file| { if file.ends_with(PathBuf::from(F_PREY_TOML)) { Some(file.to_owned()) } else { None } }) .collect() }) }) .flatten() { let package_root = pathsub::sub_paths(file.as_path(), PathBuf::from(Self::DIR_SRC).as_path()).unwrap(); self.packages.insert( package_root.to_path_buf(), PackageHandler::new(package_root, PathBuf::from(Self::DIR_TARGET))?, ); } Ok(()) } fn write_nest(&self) -> crate::Result<()> { if let Result::Ok(mut f) = OpenOptions::new() .write(true) .create_new(true) .open(F_NEST_TOML) { f.write_all(toml::to_string_pretty(&self.nest)?.as_bytes())?; } Ok(()) } 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_ver()?.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) -> std::io::Result<()> { // Make src/main/Main.java if let Result::Ok(mut f) = OpenOptions::new().write(true).create_new(true).open( PathBuf::from(Self::DIR_SRC) .join("main/java/Main") .with_extension(JAVA_EXT_SOURCE), ) { f.write_all(include_bytes!("../assets/Main.java"))?; } // Make src/test/MainTest.java if let Result::Ok(mut f) = OpenOptions::new().write(true).create_new(true).open( PathBuf::from(Self::DIR_SRC) .join("test/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"), } } }