Initial Commit.

This commit is contained in:
Olivia Brooks
2026-01-25 21:20:03 -05:00
commit ef07526d57
15 changed files with 1561 additions and 0 deletions

261
src/main.rs Normal file
View File

@@ -0,0 +1,261 @@
mod cli;
mod env;
mod fs;
mod io;
mod java;
mod nest;
use std::fs::OpenOptions;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::sync::{LazyLock, Mutex};
use cli::{CONFIG, Command};
use java::{JAVA_EXT_CLASS, JAVA_EXT_SOURCE};
use nest::{Class, NEST, NestLock};
use anyhow::Context;
use bytesize::ByteSize;
const DIR_TARGET: LazyLock<PathBuf> = LazyLock::new(|| PathBuf::from("target/"));
const DIR_TEST: LazyLock<PathBuf> = LazyLock::new(|| PathBuf::from("test/"));
const DIR_SRC: LazyLock<PathBuf> = LazyLock::new(|| PathBuf::from("src/"));
const F_NEST_TOML: LazyLock<PathBuf> = LazyLock::new(|| PathBuf::from("Nest.toml"));
const F_NEST_LOCK: LazyLock<PathBuf> = LazyLock::new(|| PathBuf::from("Nest.lock"));
/// This may not actually be the project root.
pub static PROJECT_ROOT: LazyLock<Mutex<PathBuf>> = LazyLock::new(|| {
Mutex::new(std::env::current_dir().expect("Failed to get current working directory"))
});
fn main() -> anyhow::Result<()> {
// Ensure that Nest.toml exists, if the requested command depends upon it.
if CONFIG.command.depends_on_nest() && NEST.is_err() {
println!("No Nest.toml found in project directory");
println!("Aborting...");
return Ok(());
}
match &CONFIG.command {
Command::Init => init()?,
Command::New { project_name } => {
new(project_name.to_owned())?;
init()?;
}
Command::Build => {
build()?;
}
Command::Run {
entry_point,
assertions,
} => {
build()?;
run(entry_point, assertions.into())?;
}
Command::Test { assertions } => {
test(assertions.into())?;
}
Command::Clean => clean(),
}
println!("Done.");
Ok(())
}
fn init() -> anyhow::Result<()> {
let is_empty = std::fs::read_dir(PROJECT_ROOT.lock().expect("Failed to lock mutex").as_path())
.is_ok_and(|tree| tree.count() == 0);
let project_name = PROJECT_ROOT
.lock()
.expect("Failed to lock mutex")
.file_name()
.context("Invalid directory name")?
.to_str()
.context("Unable to convert OsStr to str")?
.to_owned();
// Make src, target, tests
for dir in [DIR_SRC, DIR_TARGET, DIR_TEST] {
std::fs::create_dir(dir.clone())?;
}
// Make config file.
if let Result::Ok(mut f) = OpenOptions::new()
.write(true)
.create_new(true)
.open(F_NEST_TOML.as_path())
{
f.write_all(
toml::to_string_pretty(&nest::Nest {
package: nest::Package {
name: project_name.to_owned(),
..Default::default()
},
})?
.as_bytes(),
)?;
}
// Make .java-version
if let Result::Ok(mut f) = OpenOptions::new()
.write(true)
.create_new(true)
.open(PathBuf::from(".java-version"))
{
f.write_all(
{
let mut version = crate::env::get_javac_ver()?.major.to_string();
version.push('\n');
version
}
.as_bytes(),
)?;
}
// Make Main.java
if let Result::Ok(mut f) = OpenOptions::new()
.write(true)
.create_new(is_empty)
.open(DIR_SRC.clone().join("Main").with_extension(JAVA_EXT_SOURCE))
{
f.write_all(include_bytes!("../assets/Main.java"))?;
}
// git init .
crate::io::run_process(&["git", "init", "."])?;
// Append to .gitignore
if let Result::Ok(mut f) = OpenOptions::new()
.append(true)
.create(true)
.read(true)
.open(".gitignore")
{
dbg!();
let mut buf = String::new();
f.read_to_string(&mut buf)?;
for ignored in [
DIR_TARGET.as_path().display().to_string(),
format!("*.{}", JAVA_EXT_CLASS),
] {
if dbg!(!buf.contains(&ignored)) {
f.write(format!("{}\n", ignored).as_bytes())?;
}
}
}
Ok(())
}
fn new(project_name: String) -> anyhow::Result<()> {
let cwd = PROJECT_ROOT
.lock()
.expect("Failed to lock mutex")
.join(project_name);
std::fs::create_dir(&cwd)?;
std::env::set_current_dir(&cwd)?;
// Replace PathBuf within mutex
let mut state = PROJECT_ROOT.lock().expect("Failed to lock mutex");
*state = std::env::current_dir().expect("Failed to change current working directory");
Ok(())
}
fn build() -> anyhow::Result<()> {
let mut targets = crate::fs::expand_files(DIR_SRC.as_path())?;
let mut nest_lock = NestLock::load().unwrap_or_default();
nest_lock.update();
let mut retained_targets = vec![];
for path in targets.into_iter() {
if let Option::Some((_path, class)) = nest_lock.0.get_key_value(&path)
&& class.is_updated()
{
continue;
}
retained_targets.push(path);
}
targets = retained_targets;
let javac = java::CompilerBuilder::new()
.class_path(DIR_TARGET.as_path())
.destination(DIR_TARGET.as_path())
.build();
for target in targets {
println!("Compiling {}", target.display());
if javac.clone().compile(target.as_path()).is_ok()
&& let Result::Ok(class) = Class::try_from(target.clone())
{
nest_lock.0.insert(target, class);
}
}
OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(F_NEST_LOCK.as_path())?
.write_all(toml::to_string_pretty(&nest_lock)?.as_bytes())
.with_context(|| format!("Failed to write {}", F_NEST_LOCK.display()))
}
fn run<P: AsRef<Path>>(
entry_point: P,
assertions: bool,
) -> anyhow::Result<(Option<String>, Option<String>)> {
crate::env::set_cwd(DIR_TARGET.as_path())?;
java::JVMBuilder::new()
.assertions(assertions)
.monitor(true)
.build()
.run(entry_point)
}
fn test(assertions: bool) -> anyhow::Result<(Option<String>, Option<String>)> {
java::CompilerBuilder::new()
.class_path(DIR_TARGET.as_path())
.destination(DIR_TARGET.as_path())
.build()
.compile(DIR_TEST.as_path())?;
// Change cwd to avoid Java pathing issues.
crate::env::set_cwd(
PROJECT_ROOT
.lock()
.expect("Failed to lock mutex")
.join(DIR_TARGET.as_path()),
)?;
java::JVMBuilder::new()
.assertions(assertions)
.ram_min(ByteSize::mib(128))
.ram_max(ByteSize::mib(512))
.monitor(true)
.build()
.run(DIR_TEST.as_path())
}
fn clean() {
let _ = std::fs::remove_file(
PROJECT_ROOT
.lock()
.expect("Failed to lock mutex")
.join(F_NEST_LOCK.as_path()),
);
let _ = std::fs::remove_dir_all(
PROJECT_ROOT
.lock()
.expect("Failed to lock mutex")
.join(DIR_TARGET.as_path()),
);
}