Compare commits

..

19 Commits

Author SHA1 Message Date
Olivia Brooks
54e6350d42 Code cleanup. 2026-01-27 22:01:41 -05:00
Olivia Brooks
f3f79a12df Zed mucked something up with the merge. Fix it. 2026-01-27 21:32:18 -05:00
Olivia Brooks
649507bbcb Fix build; Correct target/ path in generated .gitignore 2026-01-27 21:30:45 -05:00
676b0b606b Merge pull request 'fix: raven test' (#2) from AdrianLong/raven:master into master
Reviewed-on: #2
2026-01-27 16:33:41 -05:00
82b45d9c66 Removed Fence denugging statements 2026-01-27 14:09:30 -05:00
05390a8f0b Fixed raven test. The raven test system needs to be redesigned in order to run multiple different test classes. Cyclic dependencies cannot be resolved the curent build system so a parser needs to be implemented to get the import statements. 2026-01-27 13:47:57 -05:00
6da7586c3e Refactored assets to use propper java syntax. Found error in the raven test command. Impropper formating when passing arguments to the java command. 2026-01-27 13:47:57 -05:00
3deedb2f7f Fixed raven build to build from anywhere inside of a raven project. 2026-01-27 13:47:57 -05:00
Olivia Brooks
3db0f53943 Correct commands. 2026-01-27 06:15:03 -05:00
Olivia Brooks
b5c4f61c8a Use a bug tracker, instead of README. 2026-01-27 06:13:37 -05:00
Olivia Brooks
aa851f7616 Add clap version, authors; Update metadata. 2026-01-26 22:21:10 -05:00
Olivia Brooks
dafc6ea885 Fix pathing issues. 2026-01-26 21:51:15 -05:00
Olivia Brooks
f6a6d54992 Filter out all non-source files before feeding to javac. 2026-01-26 21:31:35 -05:00
Olivia Brooks
a0b75edab3 Update goals and bug tracking. 2026-01-26 21:20:11 -05:00
Olivia Brooks
91bf7769b4 Create AUTHORS.md 2026-01-26 21:19:56 -05:00
Olivia Brooks
56a86b371a Support proper locating of Nest.toml; Cleanup. 2026-01-26 21:19:51 -05:00
Olivia Brooks
ac364caede Add Getting Started guide; update Future Plans; add WSL warning. 2026-01-26 14:31:34 -05:00
Olivia Brooks
7291263903 Improve cli help and add warning. 2026-01-26 14:30:41 -05:00
Olivia Brooks
11737e67f8 Fix Java project structure. 2026-01-26 14:29:43 -05:00
11 changed files with 170 additions and 102 deletions

8
AUTHORS.md Normal file
View File

@@ -0,0 +1,8 @@
# Authors
raven was written by Olivia Brooks (Cutieguwu).
Thanks to Adrian Long (Viffx) who contributed a number of PRs including:
- An initial implementation of the project root search
- Debugging my shitty code many times over

View File

@@ -2,23 +2,27 @@
name = "raven" name = "raven"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
authors = ["Olivia Brooks", "Adrian Long"]
repository = "https://gitea.cutieguwu.ca/Cutieguwu/raven"
license = "MIT"
publish = false
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
bytesize = "2.3" bytesize = "2.3"
ron = "0.12" ron = "0.12"
sha256 = "1.6.0" sha256 = "1.6"
subprocess = "0.2" subprocess = "0.2"
toml = "0.9" toml = "0.9"
[dependencies.serde]
version = "1.0"
features =["derive"]
[dependencies.clap] [dependencies.clap]
version = "4.5" version = "4.5"
features = ["derive"] features = ["cargo", "derive"]
[dependencies.semver] [dependencies.semver]
version = "1.0" version = "1.0"
features = ["serde"] features = ["serde"]
[dependencies.serde]
version = "1.0"
features =["derive"]

View File

@@ -22,9 +22,39 @@ worked out over time.
`raven` is only tested under linux. NT and Darwin support is **not** guaranteed. `raven` is only tested under linux. NT and Darwin support is **not** guaranteed.
WSL *may* affect the behaviour of raven. Quirks to be worked out.
## Getting Started
```bash
raven new demo
cd demo
raven run main.Main
```
## Raven commands
Usage: raven <COMMAND>
Commands:
new Create a new raven project
init Create a new raven project in an existing directory
build Compile the current project
run Run the current project
test !!! BORKED !!! Run the tests
clean Remove the target directory and caching
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
## Future plans ## Future plans
- [ ] Make `nest run` fall back to an entry point specified in Nest.toml, when - [ ] Make `raven run` fall back to an entry point specified in Nest.toml, when
none provided. none provided.
- [ ] Possibly add support for pulling remote packages via `nest add`. This - [ ] Fix project structure, eliminate inter-dependencies.
- [ ] Separate out `java.rs` into a dedicated wrapper library.
- [ ] Possibly add support for pulling remote packages via `raven add`. This
will be implemented should there arise a need. will be implemented should there arise a need.
- [ ] Wrap errors properly, instead of hap-hazardly using `anyhow`
- [ ] Maybe proper logging?

View File

@@ -3,4 +3,8 @@ public class Main {
public static void main(String[] args) { public static void main(String[] args) {
System.out.println("Hello, world!"); System.out.println("Hello, world!");
} }
public static int add(int a, int b) {
return a + b;
}
} }

View File

@@ -0,0 +1,10 @@
public class MainTest {
public static void main(String[] args) {
testAdd();
}
public static void testAdd() {
assert Main.add(2, 2) == 4;
}
}

View File

@@ -5,6 +5,8 @@ use clap::{ArgAction, Parser, Subcommand};
pub static CONFIG: LazyLock<Args> = LazyLock::new(|| Args::parse()); pub static CONFIG: LazyLock<Args> = LazyLock::new(|| Args::parse());
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[clap(author, version)]
#[command(help_template = "{author-section}\n{usage-heading} {usage}\n\n{all-args}")]
pub struct Args { pub struct Args {
#[command(subcommand)] #[command(subcommand)]
pub command: Command, pub command: Command,
@@ -27,12 +29,12 @@ pub enum Command {
#[clap(flatten)] #[clap(flatten)]
assertions: Assertions, assertions: Assertions,
}, },
/// Run the tests /// !!! BORKED !!! Run the tests
Test { Test {
#[clap(flatten)] #[clap(flatten)]
assertions: Assertions, assertions: Assertions,
}, },
/// Remove the target directory /// Remove the target directory and caching
Clean, Clean,
} }

View File

@@ -35,10 +35,7 @@ pub fn get_javac_ver() -> anyhow::Result<semver::Version> {
} }
pub fn set_cwd<P: AsRef<Path>>(path: P) -> io::Result<()> { pub fn set_cwd<P: AsRef<Path>>(path: P) -> io::Result<()> {
let mut cwd = crate::PROJECT_ROOT let mut cwd = crate::PROJECT_ROOT.clone();
.lock()
.expect("Failed to lock Mutex")
.to_owned();
cwd.push(path.as_ref()); cwd.push(path.as_ref());
std::env::set_current_dir(cwd) std::env::set_current_dir(cwd.as_path())
} }

View File

@@ -10,7 +10,12 @@ pub fn expand_files<P: AsRef<Path>>(path: P) -> anyhow::Result<Vec<PathBuf>> {
Ok(std::fs::read_dir(path)? Ok(std::fs::read_dir(path)?
.filter_map(|entry| { .filter_map(|entry| {
let path = entry.ok()?.path(); let path = entry.ok()?.path();
if path.is_file() { Some(path) } else { None } if path.is_dir() {
Some(expand_files(path).ok()?)
} else {
Some(vec![path])
}
}) })
.flatten()
.collect()) .collect())
} }

View File

@@ -17,11 +17,15 @@ pub struct JVMBuilder {
monitor: bool, monitor: bool,
ram_min: Option<ByteSize>, ram_min: Option<ByteSize>,
ram_max: Option<ByteSize>, ram_max: Option<ByteSize>,
class_path: PathBuf,
} }
impl JVMBuilder { impl JVMBuilder {
pub fn new() -> Self { pub fn new<P: AsRef<Path>>(class_path: P) -> Self {
Self::default() Self {
class_path: class_path.as_ref().to_path_buf(),
..Default::default()
}
} }
pub fn assertions(&mut self, assertions: bool) -> &mut Self { pub fn assertions(&mut self, assertions: bool) -> &mut Self {
@@ -46,7 +50,9 @@ impl JVMBuilder {
} }
pub fn build(&self) -> JVM { pub fn build(&self) -> JVM {
let mut flags = vec![]; let mut flags = vec![VMFlag::Classpath {
path: self.class_path.to_owned(),
}];
if self.assertions { if self.assertions {
flags.push(VMFlag::EnableAssert); flags.push(VMFlag::EnableAssert);
@@ -85,7 +91,8 @@ impl JVM {
.into_iter() .into_iter()
.flat_map(|f| Into::<Vec<String>>::into(f)), .flat_map(|f| Into::<Vec<String>>::into(f)),
); );
cmd.push(entry_point.as_ref().display().to_string());
cmd.push(entry_point.as_ref().to_path_buf().display().to_string());
let result = crate::io::run_process(cmd.as_slice()); let result = crate::io::run_process(cmd.as_slice());
@@ -110,6 +117,7 @@ impl JVM {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum VMFlag { pub enum VMFlag {
Classpath { path: PathBuf },
EnableAssert, EnableAssert,
HeapMax { size: ByteSize }, HeapMax { size: ByteSize },
HeapMin { size: ByteSize }, HeapMin { size: ByteSize },
@@ -118,12 +126,10 @@ pub enum VMFlag {
impl Into<Vec<String>> for VMFlag { impl Into<Vec<String>> for VMFlag {
fn into(self) -> Vec<String> { fn into(self) -> Vec<String> {
match self { self.to_string()
Self::EnableAssert => vec![String::from("-ea")], .split_ascii_whitespace()
Self::HeapMax { size } => vec![format!("Xmx{}", size)], .map(|s| s.to_string())
Self::HeapMin { size } => vec![format!("Xms{}", size)], .collect()
Self::Version => vec![String::from("--version")],
}
} }
} }
@@ -135,9 +141,10 @@ impl fmt::Display for VMFlag {
f, f,
"-{}", "-{}",
match self { match self {
Self::Classpath { path } => format!("classpath {}", path.display()),
Self::EnableAssert => String::from("ea"), Self::EnableAssert => String::from("ea"),
Self::HeapMax { size } => format!("Xmx{}", size), Self::HeapMax { size } => format!("Xmx{}", size.as_u64()),
Self::HeapMin { size } => format!("Xms{}", size), Self::HeapMin { size } => format!("Xms{}", size.as_u64()),
Self::Version => String::from("-version"), // --version Self::Version => String::from("-version"), // --version
} }
) )
@@ -213,14 +220,14 @@ impl Compiler {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum CompilerFlag { pub enum CompilerFlag {
Destination { path: PathBuf },
Classpath { path: PathBuf }, Classpath { path: PathBuf },
Destination { path: PathBuf },
} }
impl Into<Vec<String>> for CompilerFlag { impl Into<Vec<String>> for CompilerFlag {
fn into(self) -> Vec<String> { fn into(self) -> Vec<String> {
match self { match self {
Self::Classpath { path } => vec!["-cp".to_string(), path.display().to_string()], Self::Classpath { path } => vec!["-classpath".to_string(), path.display().to_string()],
Self::Destination { path } => vec!["-d".to_string(), path.display().to_string()], Self::Destination { path } => vec!["-d".to_string(), path.display().to_string()],
} }
} }

View File

@@ -8,7 +8,7 @@ mod nest;
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::{LazyLock, Mutex}; use std::sync::LazyLock;
use cli::{CONFIG, Command}; use cli::{CONFIG, Command};
use java::{JAVA_EXT_CLASS, JAVA_EXT_SOURCE}; use java::{JAVA_EXT_CLASS, JAVA_EXT_SOURCE};
@@ -17,17 +17,33 @@ use nest::{Class, NEST, NestLock};
use anyhow::Context; use anyhow::Context;
use bytesize::ByteSize; use bytesize::ByteSize;
const DIR_TARGET: LazyLock<PathBuf> = LazyLock::new(|| PathBuf::from("target/")); pub static PROJECT_ROOT: LazyLock<PathBuf> = LazyLock::new(|| {
const DIR_TEST: LazyLock<PathBuf> = LazyLock::new(|| PathBuf::from("test/")); // Start from CWD
const DIR_SRC: LazyLock<PathBuf> = LazyLock::new(|| PathBuf::from("src/")); let cwd = std::env::current_dir().expect("Failed to get current working directory");
let mut probe = cwd.clone();
while !probe.join(F_NEST_TOML.as_path()).exists() {
if !probe.pop() {
// This is easier than having a Result everywhere. For now.
panic!(
"No {} found in current directory or any ancestor. (cwd: {})",
F_NEST_TOML.display(),
cwd.display()
)
}
}
probe
});
const DIR_TARGET: LazyLock<PathBuf> = LazyLock::new(|| PROJECT_ROOT.join("target/"));
const DIR_SRC: LazyLock<PathBuf> = LazyLock::new(|| PROJECT_ROOT.join("src/"));
const DIR_MAIN: LazyLock<PathBuf> = LazyLock::new(|| DIR_SRC.join("main/"));
const DIR_TEST: LazyLock<PathBuf> = LazyLock::new(|| DIR_SRC.join("test/"));
const F_NEST_TOML: LazyLock<PathBuf> = LazyLock::new(|| PathBuf::from("Nest.toml")); const F_NEST_TOML: LazyLock<PathBuf> = LazyLock::new(|| PathBuf::from("Nest.toml"));
const F_NEST_LOCK: LazyLock<PathBuf> = LazyLock::new(|| PathBuf::from("Nest.lock")); 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<()> { fn main() -> anyhow::Result<()> {
// Ensure that Nest.toml exists, if the requested command depends upon it. // Ensure that Nest.toml exists, if the requested command depends upon it.
if CONFIG.command.depends_on_nest() && NEST.is_err() { if CONFIG.command.depends_on_nest() && NEST.is_err() {
@@ -64,23 +80,18 @@ fn main() -> anyhow::Result<()> {
} }
fn init() -> anyhow::Result<()> { fn init() -> anyhow::Result<()> {
let is_empty = std::fs::read_dir(PROJECT_ROOT.lock().expect("Failed to lock mutex").as_path()) let cwd = std::env::current_dir()?;
.is_ok_and(|tree| tree.count() == 0);
let project_name = PROJECT_ROOT let is_empty = std::fs::read_dir(cwd.as_path()).is_ok_and(|tree| tree.count() == 0);
.lock()
.expect("Failed to lock mutex") let project_name = cwd
.file_name() .file_name()
.context("Invalid directory name")? .context("Invalid directory name")?
.to_str() .to_str()
.context("Unable to convert OsStr to str")? .context("Unable to convert OsStr to str")?
.to_owned(); .to_owned();
// Make src, target, tests // ORDER MATTERS. THIS MUST COME FIRST.
for dir in [DIR_SRC, DIR_TARGET, DIR_TEST] {
std::fs::create_dir(dir.clone())?;
}
// Make config file. // Make config file.
if let Result::Ok(mut f) = OpenOptions::new() if let Result::Ok(mut f) = OpenOptions::new()
.write(true) .write(true)
@@ -114,13 +125,29 @@ fn init() -> anyhow::Result<()> {
)?; )?;
} }
// Make Main.java // Make src, target, tests
if let Result::Ok(mut f) = OpenOptions::new() for dir in [DIR_SRC, DIR_MAIN, DIR_TARGET, DIR_TEST] {
.write(true) std::fs::create_dir_all(dir.clone())?;
.create_new(is_empty) }
.open(DIR_SRC.clone().join("Main").with_extension(JAVA_EXT_SOURCE))
{ // Make src/main/Main.java
f.write_all(include_bytes!("../assets/Main.java"))?; if let Result::Ok(mut f) = OpenOptions::new().write(true).create_new(is_empty).open(
DIR_MAIN
.clone()
.join("Main")
.with_extension(JAVA_EXT_SOURCE),
) {
f.write_all(include_bytes!("../assets/src/main/Main.java"))?;
}
// Make src/test/MainTest.java
if let Result::Ok(mut f) = OpenOptions::new().write(true).create_new(is_empty).open(
DIR_TEST
.clone()
.join("MainTest")
.with_extension(JAVA_EXT_SOURCE),
) {
f.write_all(include_bytes!("../assets/src/test/MainTest.java"))?;
} }
// git init . // git init .
@@ -133,15 +160,14 @@ fn init() -> anyhow::Result<()> {
.read(true) .read(true)
.open(".gitignore") .open(".gitignore")
{ {
dbg!();
let mut buf = String::new(); let mut buf = String::new();
f.read_to_string(&mut buf)?; f.read_to_string(&mut buf)?;
for ignored in [ for ignored in [
DIR_TARGET.as_path().display().to_string(), format!("{}/", DIR_TARGET.file_name().unwrap().display()),
format!("*.{}", JAVA_EXT_CLASS), format!("*.{}", JAVA_EXT_CLASS),
] { ] {
if dbg!(!buf.contains(&ignored)) { if !buf.contains(&ignored) {
f.write(format!("{}\n", ignored).as_bytes())?; f.write(format!("{}\n", ignored).as_bytes())?;
} }
} }
@@ -151,23 +177,22 @@ fn init() -> anyhow::Result<()> {
} }
fn new(project_name: String) -> anyhow::Result<()> { fn new(project_name: String) -> anyhow::Result<()> {
let cwd = PROJECT_ROOT let cwd = std::env::current_dir()?.join(project_name);
.lock()
.expect("Failed to lock mutex")
.join(project_name);
std::fs::create_dir(&cwd)?; std::fs::create_dir(&cwd)?;
std::env::set_current_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(()) Ok(())
} }
fn build() -> anyhow::Result<()> { fn build() -> anyhow::Result<()> {
let mut targets = crate::fs::expand_files(DIR_SRC.as_path())?; let mut targets: Vec<PathBuf> = crate::fs::expand_files(DIR_SRC.as_path())?
.into_iter()
.filter(|f| {
f.extension()
.is_some_and(|ext| ext.to_str().is_some_and(|ext| ext == JAVA_EXT_SOURCE))
})
.collect();
let mut nest_lock = NestLock::load().unwrap_or_default(); let mut nest_lock = NestLock::load().unwrap_or_default();
nest_lock.update(); nest_lock.update();
@@ -192,7 +217,7 @@ fn build() -> anyhow::Result<()> {
for target in targets { for target in targets {
println!("Compiling {}", target.display()); println!("Compiling {}", target.display());
if javac.clone().compile(target.as_path()).is_ok() if javac.clone().compile(dbg!(target.as_path())).is_ok()
&& let Result::Ok(class) = Class::try_from(target.clone()) && let Result::Ok(class) = Class::try_from(target.clone())
{ {
nest_lock.0.insert(target, class); nest_lock.0.insert(target, class);
@@ -212,9 +237,10 @@ fn run<P: AsRef<Path>>(
entry_point: P, entry_point: P,
assertions: bool, assertions: bool,
) -> anyhow::Result<(Option<String>, Option<String>)> { ) -> anyhow::Result<(Option<String>, Option<String>)> {
// JRE pathing will be messed up without this.
crate::env::set_cwd(DIR_TARGET.as_path())?; crate::env::set_cwd(DIR_TARGET.as_path())?;
java::JVMBuilder::new() java::JVMBuilder::new(DIR_TARGET.as_path())
.assertions(assertions) .assertions(assertions)
.monitor(true) .monitor(true)
.build() .build()
@@ -229,33 +255,18 @@ fn test(assertions: bool) -> anyhow::Result<(Option<String>, Option<String>)> {
.compile(DIR_TEST.as_path())?; .compile(DIR_TEST.as_path())?;
// Change cwd to avoid Java pathing issues. // Change cwd to avoid Java pathing issues.
crate::env::set_cwd( crate::env::set_cwd(DIR_TARGET.as_path())?;
PROJECT_ROOT
.lock()
.expect("Failed to lock mutex")
.join(DIR_TARGET.as_path()),
)?;
java::JVMBuilder::new() java::JVMBuilder::new(DIR_TARGET.as_path())
.assertions(assertions) .assertions(assertions)
.ram_min(ByteSize::mib(128)) .ram_min(ByteSize::mib(128))
.ram_max(ByteSize::mib(512)) .ram_max(ByteSize::mib(512))
.monitor(true) .monitor(true)
.build() .build()
.run(DIR_TEST.as_path()) .run(DIR_TARGET.as_path())
} }
fn clean() { fn clean() {
let _ = std::fs::remove_file( let _ = std::fs::remove_file(PROJECT_ROOT.join(F_NEST_LOCK.as_path()));
PROJECT_ROOT let _ = std::fs::remove_dir_all(DIR_TARGET.join("/*").as_path());
.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()),
);
} }

View File

@@ -11,14 +11,8 @@ use anyhow::Context;
use semver::Version; use semver::Version;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub static NEST: LazyLock<anyhow::Result<Nest>> = LazyLock::new(|| { pub static NEST: LazyLock<anyhow::Result<Nest>> =
Nest::try_from( LazyLock::new(|| Nest::try_from(PROJECT_ROOT.join(F_NEST_TOML.as_path())));
PROJECT_ROOT
.lock()
.expect("Failed to lock mutex")
.join(F_NEST_TOML.as_path()),
)
});
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Nest { pub struct Nest {
@@ -133,16 +127,12 @@ impl Class {
// If the source file exists, hasn't been moved to a package (path is a file) and // 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 // the associated compiled class file exists in DIR_TARGET
PROJECT_ROOT PROJECT_ROOT
.lock()
.expect("Failed to lock mutex")
.join(DIR_TARGET.as_path()) .join(DIR_TARGET.as_path())
.join(self.name.as_path()) .join(self.name.as_path())
.with_extension(JAVA_EXT_CLASS) .with_extension(JAVA_EXT_CLASS)
.exists() .exists()
&& sha256::try_digest( && sha256::try_digest(
PROJECT_ROOT PROJECT_ROOT
.lock()
.expect("Failed to lock mutex")
.join(if self.test { .join(if self.test {
DIR_TEST.clone() DIR_TEST.clone()
} else { } else {