diff --git a/Cargo.lock b/Cargo.lock index d1c2a63..3343a9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,18 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + [[package]] name = "async-channel" version = "1.8.0" @@ -81,6 +93,20 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "blake3" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ae2468a89544a466886840aa467a25b766499f4f04bf7d9fcd10ecee9fccef" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "digest", +] + [[package]] name = "block-buffer" version = "0.10.3" @@ -208,6 +234,12 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "constant_time_eq" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ad85c1f65dc7b37604eb0e89748faf0b9653065f2a8ef69f96a687ec1e9279" + [[package]] name = "convert_case" version = "0.4.0" @@ -275,6 +307,7 @@ checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -389,7 +422,7 @@ dependencies = [ "derive_more", "full_moon_derive", "logos", - "paste", + "paste 0.1.18", "serde", "smol_str", ] @@ -787,6 +820,7 @@ dependencies = [ "hyper", "hyper-tungstenite", "lazy_static", + "lune-roblox", "mlua", "os_str_bytes", "pin-project", @@ -817,6 +851,37 @@ dependencies = [ "tokio", ] +[[package]] +name = "lune-roblox" +version = "0.5.5" +dependencies = [ + "mlua", + "rbx_binary", + "rbx_dom_weak", + "rbx_xml", + "thiserror", +] + +[[package]] +name = "lz4" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9e2dd86df36ce760a60f6ff6ad526f7ba1f14ba0356f8254fb6905e6494df1" +dependencies = [ + "libc", + "lz4-sys", +] + +[[package]] +name = "lz4-sys" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "memchr" version = "2.5.0" @@ -931,6 +996,12 @@ dependencies = [ "proc-macro-hack", ] +[[package]] +name = "paste" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" + [[package]] name = "paste-impl" version = "0.1.18" @@ -1029,6 +1100,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74605f360ce573babfe43964cbe520294dcb081afbf8c108fc6e23036b4da2df" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a1e2417ef905b8ad94215f8a607bd2d0f5d13d416d18dca4a530811e8a0674c" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "quote" version = "1.0.23" @@ -1068,6 +1158,76 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rbx_binary" +version = "0.6.6" +source = "git+https://github.com/rojo-rbx/rbx-dom?rev=ce4c5bf7b18c813417ad14cc37e5abe281dfb51a#ce4c5bf7b18c813417ad14cc37e5abe281dfb51a" +dependencies = [ + "log", + "lz4", + "profiling", + "rbx_dom_weak", + "rbx_reflection", + "rbx_reflection_database", + "thiserror", +] + +[[package]] +name = "rbx_dom_weak" +version = "2.4.0" +source = "git+https://github.com/rojo-rbx/rbx-dom?rev=ce4c5bf7b18c813417ad14cc37e5abe281dfb51a#ce4c5bf7b18c813417ad14cc37e5abe281dfb51a" +dependencies = [ + "rbx_types", + "serde", +] + +[[package]] +name = "rbx_reflection" +version = "4.2.0" +source = "git+https://github.com/rojo-rbx/rbx-dom?rev=ce4c5bf7b18c813417ad14cc37e5abe281dfb51a#ce4c5bf7b18c813417ad14cc37e5abe281dfb51a" +dependencies = [ + "rbx_types", + "serde", +] + +[[package]] +name = "rbx_reflection_database" +version = "0.2.5+roblox-530" +source = "git+https://github.com/rojo-rbx/rbx-dom?rev=ce4c5bf7b18c813417ad14cc37e5abe281dfb51a#ce4c5bf7b18c813417ad14cc37e5abe281dfb51a" +dependencies = [ + "lazy_static", + "rbx_reflection", + "rmp-serde", + "serde", +] + +[[package]] +name = "rbx_types" +version = "1.4.2" +source = "git+https://github.com/rojo-rbx/rbx-dom?rev=ce4c5bf7b18c813417ad14cc37e5abe281dfb51a#ce4c5bf7b18c813417ad14cc37e5abe281dfb51a" +dependencies = [ + "base64 0.13.1", + "bitflags", + "blake3", + "lazy_static", + "rand", + "serde", + "thiserror", +] + +[[package]] +name = "rbx_xml" +version = "0.12.4" +source = "git+https://github.com/rojo-rbx/rbx-dom?rev=ce4c5bf7b18c813417ad14cc37e5abe281dfb51a#ce4c5bf7b18c813417ad14cc37e5abe281dfb51a" +dependencies = [ + "base64 0.13.1", + "log", + "rbx_dom_weak", + "rbx_reflection", + "rbx_reflection_database", + "xml-rs", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -1157,6 +1317,28 @@ dependencies = [ "winapi", ] +[[package]] +name = "rmp" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f" +dependencies = [ + "byteorder", + "num-traits", + "paste 1.0.12", +] + +[[package]] +name = "rmp-serde" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ce7d70c926fe472aed493b902010bccc17fa9f7284145cb8772fd22fdb052d8" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -1373,6 +1555,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + [[package]] name = "syn" version = "1.0.109" @@ -1910,6 +2098,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + [[package]] name = "zeroize" version = "1.5.7" diff --git a/Cargo.toml b/Cargo.toml index 8a5cd56..0112d1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["packages/cli", "packages/lib"] +members = ["packages/cli", "packages/lib", "packages/lib-roblox"] default-members = ["packages/cli"] # Package config values shared across all packages, @@ -21,6 +21,8 @@ console = "0.15" futures-util = "0.3" lazy_static = "1.4" +mlua = { version = "0.8", features = ["luau", "serialize"] } + # Serde dependencies, supporting user-facing formats: json, yaml, toml serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["preserve_order"] } diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index f7369b1..b759388 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -14,6 +14,10 @@ categories.workspace = true name = "lune" path = "src/main.rs" +[features] +default = [] +roblox = ["lune/roblox"] + [dependencies] lune = { path = "../lib" } diff --git a/packages/lib-roblox/Cargo.toml b/packages/lib-roblox/Cargo.toml new file mode 100644 index 0000000..5009e56 --- /dev/null +++ b/packages/lib-roblox/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "lune-roblox" +publish = false +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +description.workspace = true +readme.workspace = true +keywords.workspace = true +categories.workspace = true + +[lib] +name = "lune_roblox" +path = "src/lib.rs" + +[dependencies] +mlua.workspace = true + +thiserror = "1.0" + +rbx_binary = { git = "https://github.com/rojo-rbx/rbx-dom", rev = "ce4c5bf7b18c813417ad14cc37e5abe281dfb51a" } +rbx_dom_weak = { git = "https://github.com/rojo-rbx/rbx-dom", rev = "ce4c5bf7b18c813417ad14cc37e5abe281dfb51a" } +rbx_xml = { git = "https://github.com/rojo-rbx/rbx-dom", rev = "ce4c5bf7b18c813417ad14cc37e5abe281dfb51a" } + +# TODO: Split lune lib out into something like lune-core so +# that we can use filesystem and async apis in this crate diff --git a/packages/lib-roblox/src/document/error.rs b/packages/lib-roblox/src/document/error.rs new file mode 100644 index 0000000..ccb79d4 --- /dev/null +++ b/packages/lib-roblox/src/document/error.rs @@ -0,0 +1,15 @@ +use thiserror::Error; + +#[derive(Debug, Clone, Error)] +pub enum DocumentError { + #[error("Attempted to read or write internal root document")] + InternalRootReadWrite, + #[error("Unknown document kind")] + UnknownKind, + #[error("Unknown document format")] + UnknownFormat, + #[error("Failed to read document from buffer")] + ReadError(String), + #[error("Failed to write document to buffer")] + WriteError(String), +} diff --git a/packages/lib-roblox/src/document/format.rs b/packages/lib-roblox/src/document/format.rs new file mode 100644 index 0000000..e1eb1ea --- /dev/null +++ b/packages/lib-roblox/src/document/format.rs @@ -0,0 +1,197 @@ +// Original implementation from Remodel: +// https://github.com/rojo-rbx/remodel/blob/master/src/sniff_type.rs + +use std::path::Path; + +/** + A document format specifier. + + Valid variants are the following: + + - `Binary` + - `Xml` + + Other variants are only to be used for logic internal to this crate. +*/ +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub enum DocumentFormat { + InternalRoot, + Binary, + Xml, +} + +impl DocumentFormat { + /** + Try to convert a file extension into a valid document format specifier. + + Returns `None` if the file extension is not a canonical roblox file format extension. + */ + pub fn from_extension(extension: impl AsRef) -> Option { + match extension.as_ref() { + "rbxl" | "rbxm" => Some(Self::Binary), + "rbxlx" | "rbxmx" => Some(Self::Xml), + _ => None, + } + } + + /** + Try to convert a file path into a valid document format specifier. + + Returns `None` if the file extension of the path + is not a canonical roblox file format extension. + */ + pub fn from_path(path: impl AsRef) -> Option { + match path + .as_ref() + .extension() + .map(|ext| ext.to_string_lossy()) + .as_deref() + { + Some("rbxl") | Some("rbxm") => Some(Self::Binary), + Some("rbxlx") | Some("rbxmx") => Some(Self::Xml), + _ => None, + } + } + + /** + Try to detect a document format specifier from file contents. + + Returns `None` if the file contents do not seem to be from a valid roblox file. + */ + pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Option { + let header = bytes.as_ref().get(0..8)?; + + if header.starts_with(b" Some(Self::Binary), + b' ' | b'>' => Some(Self::Xml), + _ => None, + } + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use super::*; + + #[test] + fn from_extension_binary() { + assert_eq!( + DocumentFormat::from_extension("rbxl"), + Some(DocumentFormat::Binary) + ); + + assert_eq!( + DocumentFormat::from_extension("rbxm"), + Some(DocumentFormat::Binary) + ); + } + + #[test] + fn from_extension_xml() { + assert_eq!( + DocumentFormat::from_extension("rbxlx"), + Some(DocumentFormat::Xml) + ); + + assert_eq!( + DocumentFormat::from_extension("rbxmx"), + Some(DocumentFormat::Xml) + ); + } + + #[test] + fn from_extension_invalid() { + assert_eq!(DocumentFormat::from_extension("csv"), None); + assert_eq!(DocumentFormat::from_extension("json"), None); + assert_eq!(DocumentFormat::from_extension("rbx"), None); + assert_eq!(DocumentFormat::from_extension("rbxn"), None); + assert_eq!(DocumentFormat::from_extension("xlx"), None); + assert_eq!(DocumentFormat::from_extension("xmx"), None); + } + + #[test] + fn from_path_binary() { + assert_eq!( + DocumentFormat::from_path(PathBuf::from("model.rbxl")), + Some(DocumentFormat::Binary) + ); + + assert_eq!( + DocumentFormat::from_path(PathBuf::from("model.rbxm")), + Some(DocumentFormat::Binary) + ); + } + + #[test] + fn from_path_xml() { + assert_eq!( + DocumentFormat::from_path(PathBuf::from("place.rbxlx")), + Some(DocumentFormat::Xml) + ); + + assert_eq!( + DocumentFormat::from_path(PathBuf::from("place.rbxmx")), + Some(DocumentFormat::Xml) + ); + } + + #[test] + fn from_path_invalid() { + assert_eq!( + DocumentFormat::from_path(PathBuf::from("data-file.csv")), + None + ); + assert_eq!( + DocumentFormat::from_path(PathBuf::from("nested/path/file.json")), + None + ); + assert_eq!( + DocumentFormat::from_path(PathBuf::from(".no-name-strange-rbx")), + None + ); + assert_eq!( + DocumentFormat::from_path(PathBuf::from("file_without_extension")), + None + ); + } + + #[test] + fn from_bytes_binary() { + assert_eq!( + DocumentFormat::from_bytes(b""), + Some(DocumentFormat::Xml) + ); + + assert_eq!( + DocumentFormat::from_bytes(b""), + Some(DocumentFormat::Xml) + ); + } + + #[test] + fn from_bytes_invalid() { + assert_eq!(DocumentFormat::from_bytes(b""), None); + assert_eq!(DocumentFormat::from_bytes(b" roblox"), None); + assert_eq!(DocumentFormat::from_bytes(b") -> Option { + match extension.as_ref() { + "rbxl" | "rbxlx" => Some(Self::Place), + "rbxm" | "rbxmx" => Some(Self::Model), + _ => None, + } + } + + /** + Try to convert a file path into a valid document kind specifier. + + Returns `None` if the file extension of the path + is not a canonical roblox file format extension. + */ + pub fn from_path(path: impl AsRef) -> Option { + match path + .as_ref() + .extension() + .map(|ext| ext.to_string_lossy()) + .as_deref() + { + Some("rbxl") | Some("rbxlx") => Some(Self::Place), + Some("rbxm") | Some("rbxmx") => Some(Self::Model), + _ => None, + } + } + + /** + Try to detect a document kind specifier from file contents. + + Returns `None` if the file contents do not seem to be from a valid roblox file. + */ + pub fn from_bytes(_bytes: impl AsRef<[u8]>) -> Option { + // TODO: Implement this, read comment below + todo!("Investigate if it is possible to detect document kind from contents") + } +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use super::*; + + #[test] + fn from_extension_place() { + assert_eq!( + DocumentKind::from_extension("rbxl"), + Some(DocumentKind::Place) + ); + + assert_eq!( + DocumentKind::from_extension("rbxlx"), + Some(DocumentKind::Place) + ); + } + + #[test] + fn from_extension_model() { + assert_eq!( + DocumentKind::from_extension("rbxm"), + Some(DocumentKind::Model) + ); + + assert_eq!( + DocumentKind::from_extension("rbxmx"), + Some(DocumentKind::Model) + ); + } + + #[test] + fn from_extension_invalid() { + assert_eq!(DocumentKind::from_extension("csv"), None); + assert_eq!(DocumentKind::from_extension("json"), None); + assert_eq!(DocumentKind::from_extension("rbx"), None); + assert_eq!(DocumentKind::from_extension("rbxn"), None); + assert_eq!(DocumentKind::from_extension("xlx"), None); + assert_eq!(DocumentKind::from_extension("xmx"), None); + } + + #[test] + fn from_path_place() { + assert_eq!( + DocumentKind::from_path(PathBuf::from("place.rbxl")), + Some(DocumentKind::Place) + ); + + assert_eq!( + DocumentKind::from_path(PathBuf::from("place.rbxlx")), + Some(DocumentKind::Place) + ); + } + + #[test] + fn from_path_model() { + assert_eq!( + DocumentKind::from_path(PathBuf::from("model.rbxm")), + Some(DocumentKind::Model) + ); + + assert_eq!( + DocumentKind::from_path(PathBuf::from("model.rbxmx")), + Some(DocumentKind::Model) + ); + } + + #[test] + fn from_path_invalid() { + assert_eq!( + DocumentKind::from_path(PathBuf::from("data-file.csv")), + None + ); + assert_eq!( + DocumentKind::from_path(PathBuf::from("nested/path/file.json")), + None + ); + assert_eq!( + DocumentKind::from_path(PathBuf::from(".no-name-strange-rbx")), + None + ); + assert_eq!( + DocumentKind::from_path(PathBuf::from("file_without_extension")), + None + ); + } + + // TODO: Add tests here for the from_bytes implementation +} diff --git a/packages/lib-roblox/src/document/mod.rs b/packages/lib-roblox/src/document/mod.rs new file mode 100644 index 0000000..2bff4c5 --- /dev/null +++ b/packages/lib-roblox/src/document/mod.rs @@ -0,0 +1,169 @@ +use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; + +use rbx_dom_weak::{types::Ref, WeakDom}; +use rbx_xml::{ + DecodeOptions as XmlDecodeOptions, DecodePropertyBehavior as XmlDecodePropertyBehavior, + EncodeOptions as XmlEncodeOptions, EncodePropertyBehavior as XmlEncodePropertyBehavior, +}; + +mod error; +mod format; +mod kind; + +pub use error::*; +pub use format::*; +pub use kind::*; + +#[derive(Debug, Clone)] +pub struct Document { + kind: DocumentKind, + format: DocumentFormat, + dom: Arc>, +} + +impl Document { + /** + Gets the canonical file extension for a given kind and + format of document, which will follow this chart: + + | Kind | Format | Extension | + |:------|:-------|:----------| + | Place | Binary | `rbxl` | + | Place | Xml | `rbxlx` | + | Model | Binary | `rbxm` | + | Model | Xml | `rbxmx` | + | ? | ? | None | + + The last entry here signifies any kind of internal document kind + or format variant, which should not be used outside of this crate. + + As such, if it is known that no internal specifier is being + passed here, the return value can be safely unwrapped. + */ + #[rustfmt::skip] + pub fn canonical_extension(kind: DocumentKind, format: DocumentFormat) -> Option<&'static str> { + match (kind, format) { + (DocumentKind::Place, DocumentFormat::Binary) => Some("rbxl"), + (DocumentKind::Place, DocumentFormat::Xml) => Some("rbxlx"), + (DocumentKind::Model, DocumentFormat::Binary) => Some("rbxm"), + (DocumentKind::Model, DocumentFormat::Xml) => Some("rbxmx"), + _ => None, + } + } + + /** + Decodes and creates a new document from a byte buffer. + + This will automatically handle and detect if the document should be decoded + using a roblox binary or roblox xml format, and if it is a model or place file. + */ + pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result { + let bytes = bytes.as_ref(); + let kind = DocumentKind::from_bytes(bytes).ok_or(DocumentError::UnknownKind)?; + let format = DocumentFormat::from_bytes(bytes).ok_or(DocumentError::UnknownFormat)?; + let dom = match format { + DocumentFormat::InternalRoot => Err(DocumentError::InternalRootReadWrite), + DocumentFormat::Binary => rbx_binary::from_reader(bytes) + .map_err(|err| DocumentError::ReadError(err.to_string())), + DocumentFormat::Xml => { + let xml_options = XmlDecodeOptions::new() + .property_behavior(XmlDecodePropertyBehavior::ReadUnknown); + rbx_xml::from_reader(bytes, xml_options) + .map_err(|err| DocumentError::ReadError(err.to_string())) + } + }?; + Ok(Self { + kind, + format, + dom: Arc::new(RwLock::new(dom)), + }) + } + + /** + Encodes the document as a vector of bytes, to + be written to a file or sent over the network. + + This will use the same format that the document was created + with, meaning if the document is a binary document the output + will be binary, and vice versa for xml and other future formats. + */ + pub fn to_bytes(&self) -> Result, DocumentError> { + self.to_bytes_with_format(self.format) + } + + /** + Encodes the document as a vector of bytes, to + be written to a file or sent over the network. + */ + pub fn to_bytes_with_format(&self, format: DocumentFormat) -> Result, DocumentError> { + let dom = self.dom.try_read().expect("Failed to lock dom"); + let mut bytes = Vec::new(); + match format { + DocumentFormat::InternalRoot => Err(DocumentError::InternalRootReadWrite), + DocumentFormat::Binary => rbx_binary::to_writer(&mut bytes, &dom, &[dom.root_ref()]) + .map_err(|err| DocumentError::WriteError(err.to_string())), + DocumentFormat::Xml => { + let xml_options = XmlEncodeOptions::new() + .property_behavior(XmlEncodePropertyBehavior::WriteUnknown); + rbx_xml::to_writer(&mut bytes, &dom, &[dom.root_ref()], xml_options) + .map_err(|err| DocumentError::WriteError(err.to_string())) + } + }?; + Ok(bytes) + } + + /** + Gets the kind this document was created with. + */ + pub fn kind(&self) -> DocumentKind { + self.kind + } + + /** + Gets the format this document was created with. + */ + pub fn format(&self) -> DocumentFormat { + self.format + } + + /** + Retrieves the root referent of the underlying weak dom. + */ + pub fn get_root_ref(&self) -> Ref { + let dom = self.dom.try_read().expect("Failed to lock dom"); + dom.root_ref() + } + + /** + Retrieves all root child referents of the underlying weak dom. + */ + pub fn get_root_child_refs(&self) -> Vec { + let dom = self.dom.try_read().expect("Failed to lock dom"); + dom.root().children().to_vec() + } + + /** + Retrieves a reference to the underlying weak dom. + */ + pub fn get_dom(&self) -> RwLockReadGuard { + self.dom.try_read().expect("Failed to lock dom") + } + + /** + Retrieves a mutable reference to the underlying weak dom. + */ + pub fn get_dom_mut(&mut self) -> RwLockWriteGuard { + self.dom.try_write().expect("Failed to lock dom") + } + + /** + Consumes the document, returning the underlying weak dom. + + This may panic if the document has been cloned + and still has another owner in memory. + */ + pub fn into_dom(self) -> WeakDom { + let lock = Arc::try_unwrap(self.dom).expect("Document has multiple owners in memory"); + lock.into_inner().expect("Failed to lock dom") + } +} diff --git a/packages/lib-roblox/src/lib.rs b/packages/lib-roblox/src/lib.rs new file mode 100644 index 0000000..82bd2e0 --- /dev/null +++ b/packages/lib-roblox/src/lib.rs @@ -0,0 +1,9 @@ +use mlua::prelude::*; + +pub mod document; + +pub fn module(lua: &Lua) -> LuaResult { + let exports = lua.create_table()?; + + Ok(exports) +} diff --git a/packages/lib/Cargo.toml b/packages/lib/Cargo.toml index 0b41db5..12ff91e 100644 --- a/packages/lib/Cargo.toml +++ b/packages/lib/Cargo.toml @@ -13,11 +13,18 @@ categories.workspace = true name = "lune" path = "src/lib.rs" +[features] +default = [] +roblox = ["dep:lune-roblox"] + [dependencies] +lune-roblox = { path = "../lib-roblox", optional = true } + console.workspace = true futures-util.workspace = true lazy_static.workspace = true +mlua.workspace = true serde.workspace = true serde_json.workspace = true serde_yaml.workspace = true @@ -35,7 +42,6 @@ os_str_bytes = "6.4" hyper = { version = "0.14", features = ["full"] } hyper-tungstenite = { version = "0.9" } tokio-tungstenite = { version = "0.18" } -mlua = { version = "0.8", features = ["luau", "serialize"] } dunce = "1.0" [dev-dependencies] diff --git a/packages/lib/src/globals/require.rs b/packages/lib/src/globals/require.rs index f04bb30..e4269dd 100644 --- a/packages/lib/src/globals/require.rs +++ b/packages/lib/src/globals/require.rs @@ -44,6 +44,7 @@ pub fn create(lua: &'static Lua) -> LuaResult { let require_get_abs_rel_paths = lua .create_function( |_, (require_pwd, require_source, require_path): (String, String, String)| { + // TODO: Special case for @lune prefix here let path_relative_to_pwd = PathBuf::from( &require_source .trim_start_matches("[string \"") @@ -75,6 +76,7 @@ pub fn create(lua: &'static Lua) -> LuaResult { // were async then one lua script may require a module during the file reading process let require_get_loaded_file = lua.create_function( |lua: &Lua, (path_absolute, path_relative): (String, String)| { + // TODO: Check for @lune prefix here and try to load a builtin module // Use a name without extensions for loading the chunk, the // above code assumes the require path is without extensions let path_relative_no_extension = path_relative