mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-20 20:55:50 +00:00
235 lines
6.0 KiB
Rust
235 lines
6.0 KiB
Rust
use std::path::Path;
|
|
|
|
use anyhow::Context as _;
|
|
use memofs::{IoResultExt, Vfs};
|
|
use rbx_dom_weak::ustr;
|
|
use yaml_rust2::{Yaml, YamlLoader};
|
|
|
|
use crate::{
|
|
lua_ast::{Expression, Statement},
|
|
snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot},
|
|
};
|
|
|
|
use super::meta_file::AdjacentMetadata;
|
|
|
|
pub fn snapshot_yaml(
|
|
context: &InstanceContext,
|
|
vfs: &Vfs,
|
|
path: &Path,
|
|
name: &str,
|
|
) -> anyhow::Result<Option<InstanceSnapshot>> {
|
|
let contents = vfs.read_to_string(path)?;
|
|
|
|
let mut values = YamlLoader::load_from_str(&contents)?;
|
|
let value = values
|
|
.pop()
|
|
.context("all YAML documents must contain a document")?;
|
|
if !values.is_empty() {
|
|
anyhow::bail!("Rojo does not currently support multiple documents in a YAML file")
|
|
}
|
|
|
|
let as_lua = Statement::Return(yaml_to_luau(value)?);
|
|
|
|
let meta_path = path.with_file_name(format!("{}.meta.json", name));
|
|
|
|
let mut snapshot = InstanceSnapshot::new()
|
|
.name(name)
|
|
.class_name("ModuleScript")
|
|
.property(ustr("Source"), as_lua.to_string())
|
|
.metadata(
|
|
InstanceMetadata::new()
|
|
.instigating_source(path)
|
|
.relevant_paths(vec![path.to_path_buf(), meta_path.clone()])
|
|
.context(context),
|
|
);
|
|
|
|
if let Some(meta_contents) = vfs.read(&meta_path).with_not_found()? {
|
|
let mut metadata = AdjacentMetadata::from_slice(&meta_contents, meta_path)?;
|
|
metadata.apply_all(&mut snapshot)?;
|
|
}
|
|
|
|
Ok(Some(snapshot))
|
|
}
|
|
|
|
fn yaml_to_luau(value: Yaml) -> anyhow::Result<Expression> {
|
|
const MAX_FLOAT_INT: i64 = 1 << 53;
|
|
|
|
Ok(match value {
|
|
Yaml::String(str) => Expression::String(str),
|
|
Yaml::Boolean(bool) => Expression::Bool(bool),
|
|
Yaml::Integer(int) => {
|
|
if int <= MAX_FLOAT_INT {
|
|
Expression::Number(int as f64)
|
|
} else {
|
|
anyhow::bail!(
|
|
"the integer '{int}' cannot be losslessly converted into a Luau number"
|
|
)
|
|
}
|
|
}
|
|
Yaml::Real(_) => {
|
|
let value = value.as_f64().expect("value should be a valid f64");
|
|
Expression::Number(value)
|
|
}
|
|
Yaml::Null => Expression::Nil,
|
|
Yaml::Array(values) => {
|
|
let new_values: anyhow::Result<Vec<Expression>> =
|
|
values.into_iter().map(yaml_to_luau).collect();
|
|
Expression::Array(new_values?)
|
|
}
|
|
Yaml::Hash(map) => {
|
|
let new_values: anyhow::Result<Vec<(Expression, Expression)>> = map
|
|
.into_iter()
|
|
.map(|(k, v)| {
|
|
let k = yaml_to_luau(k)?;
|
|
let v = yaml_to_luau(v)?;
|
|
Ok((k, v))
|
|
})
|
|
.collect();
|
|
Expression::table(new_values?)
|
|
}
|
|
Yaml::Alias(_) => {
|
|
anyhow::bail!("Rojo cannot convert YAML aliases to Luau")
|
|
}
|
|
Yaml::BadValue => {
|
|
anyhow::bail!("Rojo cannot convert YAML to Luau because of a parsing error")
|
|
}
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
use memofs::{InMemoryFs, VfsSnapshot};
|
|
use rbx_dom_weak::types::Variant;
|
|
|
|
#[test]
|
|
fn instance_from_vfs() {
|
|
let mut imfs = InMemoryFs::new();
|
|
imfs.load_snapshot(
|
|
"/foo.yaml",
|
|
VfsSnapshot::file(
|
|
r#"
|
|
---
|
|
string: this is a string
|
|
boolean: true
|
|
integer: 1337
|
|
float: 123456789.5
|
|
value-with-hypen: it sure is
|
|
sequence:
|
|
- wow
|
|
- 8675309
|
|
map:
|
|
key: value
|
|
key2: "value 2"
|
|
key3: 'value 3'
|
|
nested-map:
|
|
- key: value
|
|
- key2: "value 2"
|
|
- key3: 'value 3'
|
|
whatever_this_is: [i imagine, it's, a, sequence?]
|
|
null1: ~
|
|
null2: null"#,
|
|
),
|
|
)
|
|
.unwrap();
|
|
|
|
let vfs = Vfs::new(imfs.clone());
|
|
|
|
let instance_snapshot = snapshot_yaml(
|
|
&InstanceContext::default(),
|
|
&vfs,
|
|
Path::new("/foo.yaml"),
|
|
"foo",
|
|
)
|
|
.unwrap()
|
|
.unwrap();
|
|
|
|
insta::assert_yaml_snapshot!(instance_snapshot);
|
|
|
|
let source = instance_snapshot
|
|
.properties
|
|
.get(&ustr("Source"))
|
|
.expect("the result from snapshot_yaml should have a Source property");
|
|
if let Variant::String(source) = source {
|
|
insta::assert_snapshot!(source)
|
|
} else {
|
|
panic!("the Source property from snapshot_yaml was not a String")
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic(expected = "multiple documents")]
|
|
fn multiple_documents() {
|
|
let mut imfs = InMemoryFs::new();
|
|
imfs.load_snapshot(
|
|
"/foo.yaml",
|
|
VfsSnapshot::file(
|
|
r#"
|
|
---
|
|
document-1: this is a document
|
|
---
|
|
document-2: this is also a document"#,
|
|
),
|
|
)
|
|
.unwrap();
|
|
|
|
let vfs = Vfs::new(imfs.clone());
|
|
|
|
snapshot_yaml(
|
|
&InstanceContext::default(),
|
|
&vfs,
|
|
Path::new("/foo.yaml"),
|
|
"foo",
|
|
)
|
|
.unwrap()
|
|
.unwrap();
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic = "cannot be losslessly converted into a Luau number"]
|
|
fn integer_border() {
|
|
let mut imfs = InMemoryFs::new();
|
|
imfs.load_snapshot(
|
|
"/allowed.yaml",
|
|
VfsSnapshot::file(
|
|
r#"
|
|
value: 9007199254740992
|
|
"#,
|
|
),
|
|
)
|
|
.unwrap();
|
|
imfs.load_snapshot(
|
|
"/not-allowed.yaml",
|
|
VfsSnapshot::file(
|
|
r#"
|
|
value: 9007199254740993
|
|
"#,
|
|
),
|
|
)
|
|
.unwrap();
|
|
|
|
let vfs = Vfs::new(imfs.clone());
|
|
|
|
assert!(
|
|
snapshot_yaml(
|
|
&InstanceContext::default(),
|
|
&vfs,
|
|
Path::new("/allowed.yaml"),
|
|
"allowed",
|
|
)
|
|
.is_ok(),
|
|
"snapshot_yaml failed to snapshot document with integer '9007199254740992' in it"
|
|
);
|
|
|
|
snapshot_yaml(
|
|
&InstanceContext::default(),
|
|
&vfs,
|
|
Path::new("/not-allowed.yaml"),
|
|
"not-allowed",
|
|
)
|
|
.unwrap()
|
|
.unwrap();
|
|
}
|
|
}
|