lune/crates/lune-roblox/src/document/format.rs
2024-05-12 13:30:32 +02:00

202 lines
5.2 KiB
Rust

// 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 {
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
}
}
}
impl Default for DocumentFormat {
fn default() -> Self {
Self::Binary
}
}
#[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);
}
}