3 Commits

Author SHA1 Message Date
Olivia Brooks
3636916ac3 Add documentation. 2026-02-22 15:18:09 -05:00
Olivia Brooks
4e961dac40 Add documentation. 2026-02-22 13:46:03 -05:00
Olivia Brooks
fee63a2d4b Add documentation. 2026-02-22 13:45:53 -05:00
62 changed files with 456 additions and 338 deletions

0
.gitignore vendored Executable file → Normal file
View File

0
.zed/debug.json Executable file → Normal file
View File

0
.zed/settings.json Executable file → Normal file
View File

0
AUTHORS.md Executable file → Normal file
View File

0
CODE_OF_CONDUCT.md Executable file → Normal file
View File

0
Cargo.lock generated Executable file → Normal file
View File

2
Cargo.toml Executable file → Normal file
View File

@@ -35,8 +35,6 @@ ron = "0.12.0"
sha256 = "1.6" sha256 = "1.6"
subprocess = "1.0" subprocess = "1.0"
toml = "1.0" toml = "1.0"
tree-sitter = "0.22"
tree-sitter-java = "0.21.0"
derive_more = { version = "2.1", features = ["display", "from"] } derive_more = { version = "2.1", features = ["display", "from"] }
semver = { version = "1.0",features = ["serde"] } semver = { version = "1.0",features = ["serde"] }

0
LICENSE Executable file → Normal file
View File

0
README.md Executable file → Normal file
View File

3
Test1/.gitignore vendored
View File

@@ -1,3 +0,0 @@
# Automatically added by Raven
target/
*.class

10
Test1/.idea/.gitignore generated vendored
View File

@@ -1,10 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

View File

@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="app" />
<module name="core" />
</profile>
</annotationProcessing>
</component>
</project>

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://repo.maven.apache.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
</component>
</project>

12
Test1/.idea/misc.xml generated
View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="25" project-jdk-type="JavaSDK" />
</project>

6
Test1/.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View File

@@ -1 +0,0 @@
25

View File

@@ -1,4 +0,0 @@
[package]
name = "Test1"
version = "0.1.0"
entry_point = "app"

View File

@@ -1,2 +0,0 @@
target/
*.class

View File

@@ -1,7 +0,0 @@
[package]
name = "app"
version = "0.1.0"
entry_point = "com/raven/Main.java"
[dependencies]
code = {path = "../core"}

View File

@@ -1,23 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.raven</groupId>
<artifactId>multi-module-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>app</artifactId>
<dependencies>
<dependency>
<groupId>com.raven</groupId>
<artifactId>core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

View File

@@ -1,7 +0,0 @@
package com.raven;
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}

View File

@@ -1,5 +0,0 @@
public class Test {
public static void main(String[] args) {
System.out.println("This is a test.");
}
}

View File

@@ -1,2 +0,0 @@
target/
*.class

View File

@@ -1,4 +0,0 @@
[package]
name = "core"
version = "0.1.0"
entry_point = "com/raven/Main.java"

View File

@@ -1,15 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.raven</groupId>
<artifactId>multi-module-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>core</artifactId>
</project>

View File

@@ -1,7 +0,0 @@
package com.raven;
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}

View File

@@ -1,5 +0,0 @@
public class Test {
public static void main(String[] args) {
System.out.println("This is a test.");
}
}

View File

@@ -1,18 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.raven</groupId>
<artifactId>multi-module-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>core</module>
<module>app</module>
</modules>
</project>

0
crates/cli/Cargo.toml Executable file → Normal file
View File

0
crates/cli/src/lib.rs Executable file → Normal file
View File

0
crates/core/Cargo.toml Executable file → Normal file
View File

0
crates/core/assets/Main.java Executable file → Normal file
View File

0
crates/core/assets/MainTest.java Executable file → Normal file
View File

27
crates/core/src/class.rs Executable file → Normal file
View File

@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use crate::{Error, Result}; use crate::{Error, Result};
/// Data struct /// [`Class`] represents a source file. It is a handler.
#[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Class { pub struct Class {
/// Path relative to PACKAGE/java, without file extension. /// Path relative to PACKAGE/java, without file extension.
@@ -14,6 +14,13 @@ pub struct Class {
} }
impl Class { impl Class {
/// Creates a new [`Class`].
///
/// # Error
///
/// * [`Error::MismatchedPackage`] if the provided `file_path` is absolute, and cannot be
/// subtracted to just its relative path within its parent package.
/// * [`Error::Io`] if the source file cannot be loaded and digested.
pub fn new<P: AsRef<Path>>(package_src_root: P, file_path: P) -> Result<Self> { pub fn new<P: AsRef<Path>>(package_src_root: P, file_path: P) -> Result<Self> {
let mut file_path = file_path.as_ref().to_path_buf(); let mut file_path = file_path.as_ref().to_path_buf();
if file_path.is_absolute() { if file_path.is_absolute() {
@@ -27,6 +34,17 @@ impl Class {
}) })
} }
/// Returns a boolean representing if the class is up to date.
///
/// # Criteria
///
/// * The class path is local.
/// * The class has been compiled to `target/`
/// * The class' source file has not been updated.
///
/// # Errors
///
/// * [`Error::Io`]
pub fn is_updated<P: AsRef<Path>>(&self, class_path: P) -> Result<bool> { pub fn is_updated<P: AsRef<Path>>(&self, class_path: P) -> Result<bool> {
// If the path is local and that file has not been updated. // If the path is local and that file has not been updated.
Ok(class_path Ok(class_path
@@ -37,6 +55,13 @@ impl Class {
&& self.checksum == sha256::try_digest(self.path.clone())?) && self.checksum == sha256::try_digest(self.path.clone())?)
} }
/// Updates the class' checksum.
///
/// Only call this if you know that the class has been compiled successfully.
///
/// # Errors
///
/// * [`Error::Io`] if the source file cannot be loaded and digested.
pub fn update<P: AsRef<Path>>(&mut self, package_src_root: P) -> Result<()> { pub fn update<P: AsRef<Path>>(&mut self, package_src_root: P) -> Result<()> {
self.checksum = sha256::try_digest(dbg!( self.checksum = sha256::try_digest(dbg!(
package_src_root package_src_root

0
crates/core/src/dependency.rs Executable file → Normal file
View File

0
crates/core/src/error.rs Executable file → Normal file
View File

2
crates/core/src/lib.rs Executable file → Normal file
View File

@@ -1,9 +1,11 @@
pub mod class; pub mod class;
pub mod dependency; pub mod dependency;
pub mod error; pub mod error;
pub mod meta;
pub mod nest; pub mod nest;
pub mod package; pub mod package;
pub mod prelude; pub mod prelude;
pub mod prey;
pub mod workspace; pub mod workspace;
pub use error::{Error, Result}; pub use error::{Error, Result};

40
crates/core/src/meta.rs Normal file
View File

@@ -0,0 +1,40 @@
use std::path::PathBuf;
use semver::Version;
use serde::{Deserialize, Serialize};
/// Data struct
#[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Meta {
pub name: String,
pub version: Version,
#[serde(skip_serializing_if = "<Option<_>>::is_none")]
pub authors: Option<Vec<String>>,
#[serde(skip_serializing_if = "<Option<_>>::is_none")]
pub repository: Option<String>,
#[serde(skip_serializing_if = "<Option<_>>::is_none")]
pub license: Option<String>,
#[serde(skip_serializing_if = "<Option<_>>::is_none")]
pub license_file: Option<PathBuf>,
}
impl Meta {
pub fn new<S: ToString>(name: S) -> Self {
let mut meta = Self::default();
meta.name = name.to_string();
meta
}
}
impl Default for Meta {
fn default() -> Self {
Meta {
name: String::from("Main"),
version: Version::new(0, 1, 0),
authors: None,
repository: None,
license: None,
license_file: None,
}
}
}

88
crates/core/src/nest.rs Executable file → Normal file
View File

@@ -1,42 +1,30 @@
use std::collections::{BTreeMap, HashSet}; use std::collections::HashSet;
use std::fs::{File, OpenOptions}; use std::fs::{File, OpenOptions};
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use fs::expand_files;
use crate::class::Class;
use crate::dependency::Dependency; use crate::dependency::Dependency;
use crate::package::Package; use crate::meta::Meta;
//use crate::workspace::Workspace; use crate::workspace::Workspace;
pub const F_NEST_TOML: &str = "Nest.toml"; pub const F_NEST_TOML: &str = "Nest.toml";
pub const F_NEST_LOCK: &str = "Nest.lock"; pub const F_NEST_LOCK: &str = "Nest.lock";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DetailedDependency {
pub version: Option<String>,
pub path: Option<String>,
pub git: Option<String>,
pub optional: Option<bool>,
pub features: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum DependencySpec {
Simple(String), // serde = "1.0"
Detailed(DetailedDependency), // serde = { version = "...", ... }
}
/// Data struct /// Data struct
#[derive(Debug, Clone, Default, Deserialize, Serialize)] #[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
pub struct Nest { pub struct Nest {
package: Package, workspace: Workspace,
dependencies: Option<BTreeMap<String, DependencySpec>>, meta: Meta,
dependencies: HashSet<Dependency>,
} }
impl Nest { impl Nest {
pub fn new<S: ToString>(name: S) -> Self { pub fn new<S: ToString>(name: S) -> Self {
Self { Self {
package: Package::new(name), workspace: Workspace::default(),
meta: Meta::new(name),
dependencies: Default::default(), dependencies: Default::default(),
} }
} }
@@ -54,20 +42,16 @@ impl Nest {
.write_all(toml::to_string_pretty(&self)?.as_bytes())?) .write_all(toml::to_string_pretty(&self)?.as_bytes())?)
} }
pub fn with_entry_point<P: AsRef<Path>>(&mut self, entry_point: P) -> &mut Self { pub fn default_package(&self) -> PathBuf {
self.package.entry_point = Some(entry_point.as_ref().to_path_buf()); self.workspace.default_package.clone()
self
}
pub fn entry_point(&self) -> Option<PathBuf> {
self.package.entry_point.clone()
} }
pub fn name(&self) -> String { pub fn name(&self) -> String {
self.package.name.clone() self.meta.name.clone()
} }
pub fn version(&self) -> semver::Version { pub fn set_default_package<P: AsRef<Path>>(&mut self, package: P) {
self.package.version.clone() self.workspace.default_package = package.as_ref().to_path_buf();
} }
} }
@@ -75,10 +59,7 @@ impl TryFrom<PathBuf> for Nest {
type Error = crate::Error; type Error = crate::Error;
fn try_from(value: PathBuf) -> Result<Self, Self::Error> { fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
println!("{}", value.to_string_lossy());
let f = OpenOptions::new().read(true).open(value)?; let f = OpenOptions::new().read(true).open(value)?;
Self::try_from(f) Self::try_from(f)
} }
} }
@@ -96,54 +77,17 @@ impl TryFrom<File> for Nest {
/// Data struct /// Data struct
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)] #[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
pub struct NestLock { pub struct NestLock {
classes: HashSet<Class>,
pub dependencies: Vec<Dependency>, pub dependencies: Vec<Dependency>,
} }
impl NestLock { impl NestLock {
pub fn new<P: AsRef<Path>>(package_root: P, package_src_root: P) -> crate::Result<Self> {
Ok(Self::from_paths(
expand_files(package_root)?,
package_src_root,
))
}
pub fn write<P: AsRef<Path>>(&self, path: P) -> crate::Result<()> { pub fn write<P: AsRef<Path>>(&self, path: P) -> crate::Result<()> {
let mut path = path.as_ref().to_path_buf();
if path.is_dir() {
path = path.join(F_NEST_LOCK);
}
Ok(OpenOptions::new() Ok(OpenOptions::new()
.write(true) .write(true)
.create(true) .create(true)
.open(path)? .open(path)?
.write_all(toml::to_string_pretty(&self)?.as_bytes())?) .write_all(toml::to_string_pretty(&self)?.as_bytes())?)
} }
pub fn from_paths<P: AsRef<Path>>(paths: Vec<PathBuf>, package_src_root: P) -> Self {
let mut lock = Self::default();
lock.classes = paths
.iter()
.filter_map(|f| {
let dep = Class::new(package_src_root.as_ref().to_path_buf(), f.to_owned());
if dep.is_ok() {
Some(dep.unwrap())
} else {
None
}
})
.collect();
lock
}
pub fn with_class(&mut self, class: Class) -> &mut Self {
self.classes.insert(class);
self
}
pub fn classes(&mut self) -> &mut HashSet<Class> {
&mut self.classes
}
} }
impl TryFrom<PathBuf> for NestLock { impl TryFrom<PathBuf> for NestLock {

64
crates/core/src/package.rs Executable file → Normal file
View File

@@ -1,18 +1,18 @@
use std::hash::Hash; use std::hash::Hash;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use semver::Version;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::class::Class; use crate::class::Class;
use crate::nest::{F_NEST_LOCK, Nest, NestLock}; use crate::prey::{F_PREY_LOCK, F_PREY_TOML, Prey, PreyLock};
pub const DIR_JAVA: &str = "java/"; pub const DIR_JAVA: &str = "java/";
/// Hashing is only based off the Prey. /// Hashing is only based off the Prey.
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct PackageHandler { pub struct PackageHandler {
prey: Nest, prey: Prey,
prey_lock: NestLock, prey_lock: PreyLock,
/// Path relative to WORKSPACE/src /// Path relative to WORKSPACE/src
package_root: PathBuf, package_root: PathBuf,
target_dir: PathBuf, target_dir: PathBuf,
@@ -21,21 +21,21 @@ pub struct PackageHandler {
impl PackageHandler { impl PackageHandler {
pub fn new<P: AsRef<Path>>(src_dir: P, package_root: P, target_dir: P) -> crate::Result<Self> { pub fn new<P: AsRef<Path>>(src_dir: P, package_root: P, target_dir: P) -> crate::Result<Self> {
let package_root = src_dir.as_ref().join(package_root.as_ref()); let package_root = src_dir.as_ref().join(package_root.as_ref());
let prey_lock = if let Ok(prey_lock) = NestLock::try_from(package_root.join(F_NEST_LOCK)) { let prey_lock = if let Ok(prey_lock) = PreyLock::try_from(package_root.join(F_PREY_LOCK)) {
prey_lock prey_lock
} else { } else {
NestLock::new(package_root.clone(), package_root.join(DIR_JAVA))? PreyLock::new(package_root.clone(), package_root.join(DIR_JAVA))?
}; };
Ok(Self { Ok(Self {
prey: Nest::try_from(package_root.join(F_NEST_LOCK))?, prey: Prey::try_from(package_root.join(F_PREY_TOML))?,
prey_lock, prey_lock,
package_root, package_root,
target_dir: target_dir.as_ref().to_path_buf(), target_dir: target_dir.as_ref().to_path_buf(),
}) })
} }
pub fn entry_point(&self) -> Option<PathBuf> { pub fn entry_point(&self) -> PathBuf {
self.prey.entry_point() self.prey.entry_point()
} }
@@ -47,7 +47,7 @@ impl PackageHandler {
self.prey.version() self.prey.version()
} }
pub fn prey_lock(&mut self) -> &mut NestLock { pub fn prey_lock(&mut self) -> &mut PreyLock {
&mut self.prey_lock &mut self.prey_lock
} }
@@ -70,46 +70,16 @@ impl PackageHandler {
} }
} }
// impl Hash for PackageHandler { impl Hash for PackageHandler {
// fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
// self.prey.hash(state); self.prey.hash(state);
// } }
// } }
/// Data struct /// Data struct
#[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Package { pub struct Package {
pub name: String, pub entry_point: PathBuf,
pub version: Version,
#[serde(skip_serializing_if = "<Option<_>>::is_none")]
pub authors: Option<Vec<String>>,
#[serde(skip_serializing_if = "<Option<_>>::is_none")]
pub repository: Option<String>,
#[serde(skip_serializing_if = "<Option<_>>::is_none")]
pub license: Option<String>,
#[serde(skip_serializing_if = "<Option<_>>::is_none")]
pub license_file: Option<PathBuf>,
pub entry_point: Option<PathBuf>,
} }
impl Package { //impl Into<Dependency> for Package {}
pub fn new<S: ToString>(name: S) -> Self {
let mut package = Self::default();
package.name = name.to_string();
package
}
}
impl Default for Package {
fn default() -> Self {
Package {
name: String::from("Main"),
version: Version::new(0, 1, 0),
authors: None,
repository: None,
license: None,
license_file: None,
entry_point: None,
}
}
}

2
crates/core/src/prelude.rs Executable file → Normal file
View File

@@ -2,6 +2,8 @@
pub use crate::class::Class; pub use crate::class::Class;
pub use crate::dependency::Dependency; pub use crate::dependency::Dependency;
pub use crate::meta::Meta;
pub use crate::nest::{F_NEST_LOCK, F_NEST_TOML, Nest, NestLock}; pub use crate::nest::{F_NEST_LOCK, F_NEST_TOML, Nest, NestLock};
pub use crate::package::{Package, PackageHandler}; pub use crate::package::{Package, PackageHandler};
pub use crate::prey::{F_PREY_LOCK, F_PREY_TOML, Prey, PreyLock};
pub use crate::workspace::{Workspace, WorkspaceHandler}; pub use crate::workspace::{Workspace, WorkspaceHandler};

141
crates/core/src/prey.rs Normal file
View File

@@ -0,0 +1,141 @@
use std::collections::HashSet;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use fs::expand_files;
use serde::{Deserialize, Serialize};
use crate::class::Class;
use crate::meta::Meta;
use crate::package::Package;
pub const F_PREY_TOML: &str = "Prey.toml";
pub const F_PREY_LOCK: &str = "Prey.lock";
/// Data struct
#[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)]
pub struct Prey {
package: Package,
meta: Meta,
}
impl Prey {
pub fn new<S: ToString>(name: S) -> Self {
Self {
package: Package {
entry_point: PathBuf::from(""),
},
meta: Meta::new(name),
}
}
pub fn with_entry_point<P: AsRef<Path>>(&mut self, entry_point: P) -> &mut Self {
self.package.entry_point = entry_point.as_ref().to_path_buf();
self
}
pub fn entry_point(&self) -> PathBuf {
self.package.entry_point.clone()
}
pub fn name(&self) -> String {
self.meta.name.clone()
}
pub fn version(&self) -> semver::Version {
self.meta.version.clone()
}
}
impl TryFrom<PathBuf> for Prey {
type Error = crate::Error;
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
let f = OpenOptions::new().read(true).open(value)?;
Self::try_from(f)
}
}
impl TryFrom<File> for Prey {
type Error = crate::Error;
fn try_from(mut value: File) -> Result<Self, Self::Error> {
let mut buf = String::new();
value.read_to_string(&mut buf)?;
Ok(toml::from_str(buf.as_str())?)
}
}
/// Data struct
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
pub struct PreyLock {
classes: HashSet<Class>,
}
impl PreyLock {
pub fn new<P: AsRef<Path>>(package_root: P, package_src_root: P) -> crate::Result<Self> {
Ok(Self::from_paths(
expand_files(package_root)?,
package_src_root,
))
}
pub fn from_paths<P: AsRef<Path>>(paths: Vec<PathBuf>, package_src_root: P) -> Self {
let mut lock = Self::default();
lock.classes = paths
.iter()
.filter_map(|f| {
let dep = Class::new(package_src_root.as_ref().to_path_buf(), f.to_owned());
if dep.is_ok() {
Some(dep.unwrap())
} else {
None
}
})
.collect();
lock
}
pub fn write<P: AsRef<Path>>(&self, package_root: P) -> crate::Result<()> {
let mut package_root = package_root.as_ref().to_path_buf();
if package_root.is_dir() {
package_root = package_root.join(F_PREY_LOCK);
}
Ok(OpenOptions::new()
.write(true)
.create(true)
.open(package_root)?
.write_all(toml::to_string_pretty(&self)?.as_bytes())?)
}
pub fn with_class(&mut self, class: Class) -> &mut Self {
self.classes.insert(class);
self
}
pub fn classes(&mut self) -> &mut HashSet<Class> {
&mut self.classes
}
}
/// Load the PreyLock from Prey.lock file.
impl TryFrom<PathBuf> for PreyLock {
type Error = crate::Error;
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
let f = OpenOptions::new().read(true).open(value)?;
Self::try_from(f)
}
}
impl TryFrom<File> for PreyLock {
type Error = crate::Error;
fn try_from(mut value: File) -> Result<Self, Self::Error> {
let mut buf = String::new();
value.read_to_string(&mut buf)?;
Ok(toml::from_str(buf.as_str())?)
}
}

162
crates/core/src/workspace.rs Executable file → Normal file
View File

@@ -11,8 +11,13 @@ use subprocess::Exec;
use crate::nest::{F_NEST_LOCK, F_NEST_TOML, Nest, NestLock}; use crate::nest::{F_NEST_LOCK, F_NEST_TOML, Nest, NestLock};
use crate::package::PackageHandler; use crate::package::PackageHandler;
use crate::prey::{F_PREY_LOCK, F_PREY_TOML, Prey};
use crate::{Error, package}; use crate::{Error, package};
/// Represents a raven workspace.
///
/// [`WorkspaceHandler`] manages project compilation, cache writing,
/// and tracking of all packages that make up the workspace.
#[derive(Debug)] #[derive(Debug)]
pub struct WorkspaceHandler { pub struct WorkspaceHandler {
nest: Nest, nest: Nest,
@@ -22,9 +27,16 @@ pub struct WorkspaceHandler {
} }
impl WorkspaceHandler { impl WorkspaceHandler {
const DIR_SRC: &'static str = "src/"; const DIR_SRC: &str = "src/";
const DIR_TARGET: &'static str = "target/"; const DIR_TARGET: &str = "target/";
/// Creates a new [`WorkspaceHandler`] instance at the provided path.
///
/// # Errors
///
/// * [`Error::Io`] if the `project_root` cannot be canonicalized.
/// * [`Error::MissingFileName`] if a name for the workspace cannot be derived
/// from the `project_root`
pub fn new<P: AsRef<Path>>(project_root: P) -> crate::Result<Self> { pub fn new<P: AsRef<Path>>(project_root: P) -> crate::Result<Self> {
let project_root = project_root.as_ref().canonicalize()?; let project_root = project_root.as_ref().canonicalize()?;
@@ -41,10 +53,16 @@ impl WorkspaceHandler {
}) })
} }
/// Loads an existing workspace from the provided path.
///
/// # Errors
///
/// * [`Error::Io`] for any number of IO-related errors.
/// Unwrap the [`std::io::Error`] for further information.
/// * [`Error::MissingFileName`] if a name for the workspace cannot be derived
/// from the `project_root`
pub fn load<P: AsRef<Path>>(project_root: P) -> crate::Result<Self> { pub fn load<P: AsRef<Path>>(project_root: P) -> crate::Result<Self> {
let project_root = project_root.as_ref().canonicalize()?; let project_root = project_root.as_ref().canonicalize()?;
println!("{}", project_root.display());
let mut workspace_manager = Self { let mut workspace_manager = Self {
nest: Nest::try_from(project_root.join(F_NEST_TOML))?, nest: Nest::try_from(project_root.join(F_NEST_TOML))?,
@@ -58,6 +76,7 @@ impl WorkspaceHandler {
Ok(workspace_manager) Ok(workspace_manager)
} }
/// Writes all lock files (Nest.toml, src/**/Prey.toml) to the disk.
pub fn write_locks(&self) -> crate::Result<()> { pub fn write_locks(&self) -> crate::Result<()> {
if let Option::Some(lock) = self.nest_lock.clone() { if let Option::Some(lock) = self.nest_lock.clone() {
lock.write(self.project_root.join(F_NEST_LOCK))?; lock.write(self.project_root.join(F_NEST_LOCK))?;
@@ -70,6 +89,26 @@ impl WorkspaceHandler {
Ok(()) Ok(())
} }
/// Initializes a new workspace.
///
/// # Behaviour
///
/// Init will avoid overwriting or adding an example project if raven is being integrated into
/// an existing workspace codebase.
///
/// Init will always write:
///
/// * Nest.toml
/// * .java-version
/// * Attempt to append to .gitignore
///
/// If this is an empty workspace, init will include:
///
/// * Calling `git init`
/// * Writing an example project.
/// * Automatic package discovery.
///
/// # Errors
pub fn init(&mut self) -> crate::Result<&mut Self> { pub fn init(&mut self) -> crate::Result<&mut Self> {
// ORDER MATTERS. // ORDER MATTERS.
let is_empty = read_dir(self.project_root.as_path()).is_ok_and(|tree| tree.count() == 0); let is_empty = read_dir(self.project_root.as_path()).is_ok_and(|tree| tree.count() == 0);
@@ -115,15 +154,26 @@ impl WorkspaceHandler {
Ok(self) Ok(self)
} }
// This is the naive build /// This is a naive build; it attempts to build every source file individually.
///
/// It calls [`PackageHandler::get_outdated()`] on all known packages to determine
/// compile targets.
///
/// It will then attempt to write all locks to update the caches and avoid attempting to
/// needlessly recompile targets.
///
/// # Errors
///
/// * [`Error::AbsentPrimeError`]
/// * [`Error::Io`]
pub fn build(&mut self) -> crate::Result<&mut Self> { pub fn build(&mut self) -> crate::Result<&mut Self> {
let compiler = java::compiler::CompilerBuilder::new() let compiler = java::compiler::CompilerBuilder::new()
.class_path(Self::DIR_TARGET) .class_path(Self::DIR_TARGET)
.destination(Self::DIR_TARGET) .destination(Self::DIR_TARGET)
.build(); .build();
// cyclic dependencies across packages are not supported. // No unintentional deep copying anywhere, right?
// construct a hashmap of parent child relationships between all files across all packages. // Probably not. We'll see.
for handler in self.packages.values_mut() { for handler in self.packages.values_mut() {
let package_src_root = self let package_src_root = self
.project_root .project_root
@@ -136,7 +186,7 @@ impl WorkspaceHandler {
dbg!(&target); dbg!(&target);
if let Ok(true) = compiler if let Ok(true) = compiler
.clone() .clone()
.compile(vec![package_src_root.join(target.path.as_path())]) .compile(package_src_root.join(target.path.as_path()))
{ {
target.update(package_src_root.as_path())?; target.update(package_src_root.as_path())?;
handler handler
@@ -153,50 +203,60 @@ impl WorkspaceHandler {
Ok(self) Ok(self)
} }
/// Attempts to call the JVM to run, in order of precedence:
///
/// 1. Provided entry point which includes a specific source file.
/// 2. Provided entry point to a package, using that package's default entry point
/// defined in it's `Prey.toml`
/// 3. The workspace's default package defined in `Nest.toml`, and that package's default
/// entry point as defined in it's `Prey.toml`.
///
/// # Errors
///
/// * [`Error::UnknownPackage`] if the package cannot be found.
/// * [`Error::UndefinedEntryPoint`] if no fallback entry point can be found.
/// * [`Error::Io`]
/// * [`Error::Java`]
pub fn run<P: AsRef<Path>>( pub fn run<P: AsRef<Path>>(
&mut self, &mut self,
entry_point: Option<P>, entry_point: Option<P>,
assertions: bool, assertions: bool,
) -> crate::Result<&mut Self> { ) -> crate::Result<&mut Self> {
let mut entry_point = entry_point.expect("No entry point found").as_ref().to_path_buf(); let mut entry_point = if entry_point.is_none() {
while entry_point.is_file() { self.nest.default_package()
} 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 entry_point = self
.packages .packages
.get(&entry_point) .get(&entry_point)
.ok_or(Error::UnknownPackage)? .ok_or(Error::UnknownPackage)?
.entry_point() .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() { if entry_point.file_name().is_none() {
return Err(Error::UndefinedEntryPoint); return Err(Error::UndefinedEntryPoint);
} }
//
java::runtime::JVMBuilder::new(Self::DIR_TARGET) java::runtime::JVMBuilder::new(Self::DIR_TARGET)
.assertions(assertions) .assertions(assertions)
.monitor(true) .monitor(true)
.build() .build()
.run(entry_point)?; .run(entry_point)?;
Ok(self) Ok(self)
} }
/// Cleans the workspace of it's caches:
///
/// * `target/`
/// * Nest.lock
/// * Prey.lock
pub fn clean(&mut self) -> crate::Result<&mut Self> { pub fn clean(&mut self) -> crate::Result<&mut Self> {
// Clear Nest.lock // Clear Nest.lock
if let Err(err) = std::fs::remove_file(self.project_root.join(F_NEST_LOCK)) { if let Err(err) = std::fs::remove_file(self.project_root.join(F_NEST_LOCK)) {
@@ -211,7 +271,7 @@ impl WorkspaceHandler {
self.project_root self.project_root
.join(Self::DIR_SRC) .join(Self::DIR_SRC)
.join(handler.name()) .join(handler.name())
.join(F_NEST_LOCK), .join(F_PREY_LOCK),
) { ) {
if err.kind() != std::io::ErrorKind::NotFound { if err.kind() != std::io::ErrorKind::NotFound {
return Err(Error::from(err)); return Err(Error::from(err));
@@ -225,10 +285,14 @@ impl WorkspaceHandler {
Ok(self) Ok(self)
} }
/// Add any newly created packages. /// Scan for newly created packages, wrapping them in [`PackageHandler`]s
/// Prey.toml files designate packages. /// and adding them to the [`WorkspaceHandler`]'s package map.
/// discover_packages() iterates the filesystem recursively to discover all such packages. ///
/// discovered packages are stored as a hashmap<PathBuf, PackageHandler> in the WorkspaceHandler. /// Packages are identified by their `Prey.toml` manifests.
///
/// # Errors
///
/// * [`Error::Io`]
fn discover_packages(&mut self) -> crate::Result<()> { fn discover_packages(&mut self) -> crate::Result<()> {
// Scan the src/ directory for entries, // Scan the src/ directory for entries,
// filter out the files, // filter out the files,
@@ -240,7 +304,7 @@ impl WorkspaceHandler {
// whole subtree. // whole subtree.
// //
// Yes, I know this looks like shit. // Yes, I know this looks like shit.
// That's because it is. CON-FUCKING-FIRMED THIS SHIT IS NOT DEBUGGABLE // That's because it is.
for file in read_dir(self.project_root.join(Self::DIR_SRC))? for file in read_dir(self.project_root.join(Self::DIR_SRC))?
// Get directories // Get directories
@@ -253,14 +317,14 @@ impl WorkspaceHandler {
}) })
// Get Prey.toml files // Get Prey.toml files
.filter_map(|dir| { .filter_map(|dir| {
Some(if dir.join(F_NEST_TOML).exists() { Some(if dir.join(F_PREY_TOML).exists() {
vec![dir.join(F_NEST_TOML)] vec![dir.join(F_PREY_TOML)]
} else { } else {
expand_files(dir) expand_files(dir)
.ok()? .ok()?
.iter() .iter()
.filter_map(|file| { .filter_map(|file| {
if file.ends_with(PathBuf::from(F_NEST_TOML)) { if file.ends_with(PathBuf::from(F_PREY_TOML)) {
Some(file.to_owned()) Some(file.to_owned())
} else { } else {
None None
@@ -293,10 +357,18 @@ impl WorkspaceHandler {
Ok(()) Ok(())
} }
/// Writes the [`WorkspaceHandler`]'s `Nest.toml` to disk.
fn write_nest(&self) -> crate::Result<()> { fn write_nest(&self) -> crate::Result<()> {
Ok(self.nest.write(self.project_root.clone())?) Ok(self.nest.write(self.project_root.clone())?)
} }
/// Writes a `.java-version` file for `jenv` to the disk with an automatically parsed
/// java version from [`java::get_javac_version()`].
///
/// # Errors
///
/// * [`Error::Io`]
/// * [`Error::Java`]
fn write_java_version(&self) -> crate::Result<()> { fn write_java_version(&self) -> crate::Result<()> {
if let Result::Ok(mut f) = OpenOptions::new() if let Result::Ok(mut f) = OpenOptions::new()
.write(true) .write(true)
@@ -309,6 +381,7 @@ impl WorkspaceHandler {
Ok(()) Ok(())
} }
/// Writes an example project tree to disk.
fn write_dir_tree(&self) -> std::io::Result<()> { fn write_dir_tree(&self) -> std::io::Result<()> {
for dir in [ for dir in [
format!("{}main/java", Self::DIR_SRC), format!("{}main/java", Self::DIR_SRC),
@@ -321,6 +394,7 @@ impl WorkspaceHandler {
Ok(()) Ok(())
} }
/// Writes an example project to disk.
fn write_example_project(&self) -> crate::Result<()> { fn write_example_project(&self) -> crate::Result<()> {
let main: PathBuf = PathBuf::from(Self::DIR_SRC).join("main/"); let main: PathBuf = PathBuf::from(Self::DIR_SRC).join("main/");
let test: PathBuf = PathBuf::from(Self::DIR_SRC).join("test/"); let test: PathBuf = PathBuf::from(Self::DIR_SRC).join("test/");
@@ -332,10 +406,10 @@ impl WorkspaceHandler {
if let Result::Ok(mut f) = OpenOptions::new() if let Result::Ok(mut f) = OpenOptions::new()
.write(true) .write(true)
.create_new(true) .create_new(true)
.open(main.join(F_NEST_TOML)) .open(main.join(F_PREY_TOML))
{ {
f.write_all( f.write_all(
toml::to_string_pretty(&Nest::new("main").with_entry_point("Main"))?.as_bytes(), toml::to_string_pretty(&Prey::new("main").with_entry_point("Main"))?.as_bytes(),
)?; )?;
} }
@@ -352,10 +426,10 @@ impl WorkspaceHandler {
if let Result::Ok(mut f) = OpenOptions::new() if let Result::Ok(mut f) = OpenOptions::new()
.write(true) .write(true)
.create_new(true) .create_new(true)
.open(test.join(F_NEST_TOML)) .open(test.join(F_PREY_TOML))
{ {
f.write_all( f.write_all(
toml::to_string_pretty(&Nest::new("test").with_entry_point("MainTest"))?.as_bytes(), toml::to_string_pretty(&Prey::new("test").with_entry_point("MainTest"))?.as_bytes(),
)?; )?;
} }

0
crates/fs/Cargo.toml Executable file → Normal file
View File

0
crates/fs/src/lib.rs Executable file → Normal file
View File

0
crates/java/Cargo.toml Executable file → Normal file
View File

4
crates/java/src/compiler.rs Executable file → Normal file
View File

@@ -50,7 +50,7 @@ pub struct Compiler {
} }
impl Compiler { impl Compiler {
pub fn compile<P: AsRef<Path>>(self, paths: Vec<P>) -> Result<bool> { pub fn compile<P: AsRef<Path>>(self, path: P) -> Result<bool> {
Ok(Exec::cmd(JAVA_BIN_COMPILER) Ok(Exec::cmd(JAVA_BIN_COMPILER)
.args( .args(
self.flags self.flags
@@ -58,7 +58,7 @@ impl Compiler {
.into_iter() .into_iter()
.flat_map(|f| Into::<Vec<String>>::into(f)), .flat_map(|f| Into::<Vec<String>>::into(f)),
) )
.args(paths.into_iter().map(|p| p.as_ref().with_extension(JAVA_EXT_SOURCE))) .arg(path.as_ref().with_extension(JAVA_EXT_SOURCE))
.stdout(Redirection::Pipe) .stdout(Redirection::Pipe)
.detached() .detached()
.start()? .start()?

0
crates/java/src/error.rs Executable file → Normal file
View File

1
crates/java/src/lib.rs Executable file → Normal file
View File

@@ -1,7 +1,6 @@
pub mod compiler; pub mod compiler;
pub mod error; pub mod error;
pub mod runtime; pub mod runtime;
mod compilation_scheduler;
use std::str::FromStr; use std::str::FromStr;

0
crates/java/src/runtime.rs Executable file → Normal file
View File

0
crates/pom/Cargo.toml Executable file → Normal file
View File

0
crates/pom/src/error.rs Executable file → Normal file
View File

0
crates/pom/src/lib.rs Executable file → Normal file
View File

0
crates/pom/src/xml.rs Executable file → Normal file
View File

0
crates/raven/Cargo.lock generated Executable file → Normal file
View File

0
crates/raven/Cargo.toml Executable file → Normal file
View File

12
crates/raven/src/main.rs Executable file → Normal file
View File

@@ -4,15 +4,17 @@ use anyhow::anyhow;
use cli::{CLI_ARGS, Command}; use cli::{CLI_ARGS, Command};
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
// type_ is yet unused. // type_ is yet unused.
// `raven new` requires special behaviour.
// This will call the "new" part of the command,
// changing the cwd, before calling WorkspaceHandler.init()
// on the actual workspace directory.
if let Command::New { project_name, .. } = &CLI_ARGS.command { if let Command::New { project_name, .. } = &CLI_ARGS.command {
new(project_name.to_owned())?; new(project_name.to_owned())?;
} }
let project_root = std::env::current_dir()?; let project_root = std::env::current_dir()?;
println!("{}", project_root.display());
let mut wh: WorkspaceHandler = match &CLI_ARGS.command { let mut wh: WorkspaceHandler = match &CLI_ARGS.command {
Command::New { .. } | Command::Init => WorkspaceHandler::new(project_root), Command::New { .. } | Command::Init => WorkspaceHandler::new(project_root),
@@ -20,7 +22,6 @@ fn main() -> anyhow::Result<()> {
} }
.map_err(|err| anyhow!(err))?; .map_err(|err| anyhow!(err))?;
match &CLI_ARGS.command { match &CLI_ARGS.command {
Command::New { .. } | Command::Init => wh.init(), Command::New { .. } | Command::Init => wh.init(),
Command::Build => wh.build(), Command::Build => wh.build(),
@@ -39,7 +40,10 @@ fn main() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
/// Creates a new project directory and enters it.
fn new(project_name: String) -> anyhow::Result<()> { fn new(project_name: String) -> anyhow::Result<()> {
// Because this messes around with the CWD, this should live external to any
// instance of a `WorkspaceHandler`.
let cwd = std::env::current_dir()?.join(project_name); let cwd = std::env::current_dir()?.join(project_name);
std::fs::create_dir(&cwd)?; std::fs::create_dir(&cwd)?;

0
templates/demo/Nest.toml Executable file → Normal file
View File

0
templates/demo/src/main/Prey.lock Executable file → Normal file
View File

0
templates/demo/src/main/Prey.toml Executable file → Normal file
View File

84
templates/pom.xml Normal file
View File

@@ -0,0 +1,84 @@
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<modelVersion>4.0.0</modelVersion>
<groupId>com.viffx</groupId>
<artifactId>GameEngine</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<lwjgl.version>3.4.0</lwjgl.version>
<lwjgl.natives>natives-windows</lwjgl.natives>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-bom</artifactId>
<version>${lwjgl.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl</artifactId>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-assimp</artifactId>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-glfw</artifactId>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-openal</artifactId>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-stb</artifactId>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-vulkan</artifactId>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl</artifactId>
<classifier>${lwjgl.natives}</classifier>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-assimp</artifactId>
<classifier>${lwjgl.natives}</classifier>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-glfw</artifactId>
<classifier>${lwjgl.natives}</classifier>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-openal</artifactId>
<classifier>${lwjgl.natives}</classifier>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-stb</artifactId>
<classifier>${lwjgl.natives}</classifier>
</dependency>
</dependencies>
</project>