mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 13:00:37 +00:00
Start work on roblox feature, implement document struct
This commit is contained in:
parent
3163f33887
commit
5bdc968ffe
11 changed files with 781 additions and 3 deletions
196
Cargo.lock
generated
196
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -14,6 +14,10 @@ categories.workspace = true
|
|||
name = "lune"
|
||||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
roblox = ["lune/roblox"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
lune = { path = "../lib" }
|
||||
|
|
27
packages/lib-roblox/Cargo.toml
Normal file
27
packages/lib-roblox/Cargo.toml
Normal 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
|
15
packages/lib-roblox/src/document/error.rs
Normal file
15
packages/lib-roblox/src/document/error.rs
Normal 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),
|
||||
}
|
197
packages/lib-roblox/src/document/format.rs
Normal file
197
packages/lib-roblox/src/document/format.rs
Normal 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);
|
||||
}
|
||||
}
|
153
packages/lib-roblox/src/document/kind.rs
Normal file
153
packages/lib-roblox/src/document/kind.rs
Normal 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
|
||||
}
|
169
packages/lib-roblox/src/document/mod.rs
Normal file
169
packages/lib-roblox/src/document/mod.rs
Normal 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")
|
||||
}
|
||||
}
|
9
packages/lib-roblox/src/lib.rs
Normal file
9
packages/lib-roblox/src/lib.rs
Normal 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)
|
||||
}
|
|
@ -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]
|
||||
|
|
|
@ -44,6 +44,7 @@ pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
|
|||
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<LuaTable> {
|
|||
// 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
|
||||
|
|
Loading…
Reference in a new issue