Start work on roblox feature, implement document struct

This commit is contained in:
Filip Tibell 2023-03-09 12:17:25 +01:00
parent 3163f33887
commit 5bdc968ffe
No known key found for this signature in database
11 changed files with 781 additions and 3 deletions

196
Cargo.lock generated
View file

@ -8,6 +8,18 @@ version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" 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]] [[package]]
name = "async-channel" name = "async-channel"
version = "1.8.0" version = "1.8.0"
@ -81,6 +93,20 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 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]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.3" version = "0.10.3"
@ -208,6 +234,12 @@ dependencies = [
"windows-sys 0.42.0", "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]] [[package]]
name = "convert_case" name = "convert_case"
version = "0.4.0" version = "0.4.0"
@ -275,6 +307,7 @@ checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
dependencies = [ dependencies = [
"block-buffer", "block-buffer",
"crypto-common", "crypto-common",
"subtle",
] ]
[[package]] [[package]]
@ -389,7 +422,7 @@ dependencies = [
"derive_more", "derive_more",
"full_moon_derive", "full_moon_derive",
"logos", "logos",
"paste", "paste 0.1.18",
"serde", "serde",
"smol_str", "smol_str",
] ]
@ -787,6 +820,7 @@ dependencies = [
"hyper", "hyper",
"hyper-tungstenite", "hyper-tungstenite",
"lazy_static", "lazy_static",
"lune-roblox",
"mlua", "mlua",
"os_str_bytes", "os_str_bytes",
"pin-project", "pin-project",
@ -817,6 +851,37 @@ dependencies = [
"tokio", "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]] [[package]]
name = "memchr" name = "memchr"
version = "2.5.0" version = "2.5.0"
@ -931,6 +996,12 @@ dependencies = [
"proc-macro-hack", "proc-macro-hack",
] ]
[[package]]
name = "paste"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
[[package]] [[package]]
name = "paste-impl" name = "paste-impl"
version = "0.1.18" version = "0.1.18"
@ -1029,6 +1100,25 @@ dependencies = [
"unicode-ident", "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]] [[package]]
name = "quote" name = "quote"
version = "1.0.23" version = "1.0.23"
@ -1068,6 +1158,76 @@ dependencies = [
"getrandom", "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]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.16" version = "0.2.16"
@ -1157,6 +1317,28 @@ dependencies = [
"winapi", "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]] [[package]]
name = "rustc-hash" name = "rustc-hash"
version = "1.1.0" version = "1.1.0"
@ -1373,6 +1555,12 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "subtle"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.109" version = "1.0.109"
@ -1910,6 +2098,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "xml-rs"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
[[package]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.5.7" version = "1.5.7"

View file

@ -1,5 +1,5 @@
[workspace] [workspace]
members = ["packages/cli", "packages/lib"] members = ["packages/cli", "packages/lib", "packages/lib-roblox"]
default-members = ["packages/cli"] default-members = ["packages/cli"]
# Package config values shared across all packages, # Package config values shared across all packages,
@ -21,6 +21,8 @@ console = "0.15"
futures-util = "0.3" futures-util = "0.3"
lazy_static = "1.4" lazy_static = "1.4"
mlua = { version = "0.8", features = ["luau", "serialize"] }
# Serde dependencies, supporting user-facing formats: json, yaml, toml # Serde dependencies, supporting user-facing formats: json, yaml, toml
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["preserve_order"] } serde_json = { version = "1.0", features = ["preserve_order"] }

View file

@ -14,6 +14,10 @@ categories.workspace = true
name = "lune" name = "lune"
path = "src/main.rs" path = "src/main.rs"
[features]
default = []
roblox = ["lune/roblox"]
[dependencies] [dependencies]
lune = { path = "../lib" } lune = { path = "../lib" }

View file

@ -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

View file

@ -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),
}

View file

@ -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<str>) -> Option<Self> {
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<Path>) -> Option<Self> {
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<Self> {
let header = bytes.as_ref().get(0..8)?;
if header.starts_with(b"<roblox") {
match header[7] {
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"<roblox!hello"),
Some(DocumentFormat::Binary)
);
assert_eq!(
DocumentFormat::from_bytes(b"<roblox!"),
Some(DocumentFormat::Binary)
);
}
#[test]
fn from_bytes_xml() {
assert_eq!(
DocumentFormat::from_bytes(b"<roblox xml:someschemajunk>"),
Some(DocumentFormat::Xml)
);
assert_eq!(
DocumentFormat::from_bytes(b"<roblox>"),
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"<roblox"), None);
assert_eq!(DocumentFormat::from_bytes(b"<roblox-"), None);
}
}

View file

@ -0,0 +1,153 @@
use std::path::Path;
/**
A document kind specifier.
Valid variants are the following:
- `Model`
- `Place`
Other variants are only to be used for logic internal to this crate.
*/
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum DocumentKind {
InternalRoot,
Place,
Model,
}
impl DocumentKind {
/**
Try to convert a file extension into a valid document kind specifier.
Returns `None` if the file extension is not a canonical roblox file format extension.
*/
pub fn from_extension(extension: impl AsRef<str>) -> Option<Self> {
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<Path>) -> Option<Self> {
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<Self> {
// 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
}

View file

@ -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<RwLock<WeakDom>>,
}
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<Self, DocumentError> {
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<Vec<u8>, 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<Vec<u8>, 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<Ref> {
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<WeakDom> {
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<WeakDom> {
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")
}
}

View file

@ -0,0 +1,9 @@
use mlua::prelude::*;
pub mod document;
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
let exports = lua.create_table()?;
Ok(exports)
}

View file

@ -13,11 +13,18 @@ categories.workspace = true
name = "lune" name = "lune"
path = "src/lib.rs" path = "src/lib.rs"
[features]
default = []
roblox = ["dep:lune-roblox"]
[dependencies] [dependencies]
lune-roblox = { path = "../lib-roblox", optional = true }
console.workspace = true console.workspace = true
futures-util.workspace = true futures-util.workspace = true
lazy_static.workspace = true lazy_static.workspace = true
mlua.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
serde_yaml.workspace = true serde_yaml.workspace = true
@ -35,7 +42,6 @@ os_str_bytes = "6.4"
hyper = { version = "0.14", features = ["full"] } hyper = { version = "0.14", features = ["full"] }
hyper-tungstenite = { version = "0.9" } hyper-tungstenite = { version = "0.9" }
tokio-tungstenite = { version = "0.18" } tokio-tungstenite = { version = "0.18" }
mlua = { version = "0.8", features = ["luau", "serialize"] }
dunce = "1.0" dunce = "1.0"
[dev-dependencies] [dev-dependencies]

View file

@ -44,6 +44,7 @@ pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
let require_get_abs_rel_paths = lua let require_get_abs_rel_paths = lua
.create_function( .create_function(
|_, (require_pwd, require_source, require_path): (String, String, String)| { |_, (require_pwd, require_source, require_path): (String, String, String)| {
// TODO: Special case for @lune prefix here
let path_relative_to_pwd = PathBuf::from( let path_relative_to_pwd = PathBuf::from(
&require_source &require_source
.trim_start_matches("[string \"") .trim_start_matches("[string \"")
@ -75,6 +76,7 @@ pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
// were async then one lua script may require a module during the file reading process // were async then one lua script may require a module during the file reading process
let require_get_loaded_file = lua.create_function( let require_get_loaded_file = lua.create_function(
|lua: &Lua, (path_absolute, path_relative): (String, String)| { |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 // Use a name without extensions for loading the chunk, the
// above code assumes the require path is without extensions // above code assumes the require path is without extensions
let path_relative_no_extension = path_relative let path_relative_no_extension = path_relative