diff --git a/CHANGELOG.md b/CHANGELOG.md index 6df231e4..b635a506 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Rojo Changelog ## [Unreleased] +* Added support for `.model.json` files, compatible with 0.4.x * Fixed in-memory filesystem not handling out-of-order filesystem change events * Fixed long-polling error caused by a promise mixup ([#110](https://github.com/LPGhatguy/rojo/issues/110)) diff --git a/server/src/rbx_snapshot.rs b/server/src/rbx_snapshot.rs index c1f4c39a..fe3a2637 100644 --- a/server/src/rbx_snapshot.rs +++ b/server/src/rbx_snapshot.rs @@ -6,10 +6,11 @@ use std::{ str, }; -use serde_derive::{Serialize, Deserialize}; +use failure::Fail; +use log::info; use maplit::hashmap; use rbx_tree::{RbxTree, RbxValue, RbxInstanceProperties}; -use failure::Fail; +use serde_derive::{Serialize, Deserialize}; use crate::{ imfs::{ @@ -53,6 +54,11 @@ pub enum SnapshotError { path: PathBuf, }, + JsonModelDecodeError { + inner: serde_json::Error, + path: PathBuf, + }, + XmlModelDecodeError { inner: rbx_xml::DecodeError, path: PathBuf, @@ -71,6 +77,9 @@ impl fmt::Display for SnapshotError { SnapshotError::Utf8Error { inner, path } => { write!(output, "Invalid UTF-8: {} in path {}", inner, path.display()) }, + SnapshotError::JsonModelDecodeError { inner, path } => { + write!(output, "Malformed .model.json model: {:?} in path {}", inner, path.display()) + }, SnapshotError::XmlModelDecodeError { inner, path } => { write!(output, "Malformed rbxmx model: {:?} in path {}", inner, path.display()) }, @@ -248,7 +257,18 @@ fn snapshot_imfs_file<'source>( Some("txt") => snapshot_txt_file(file)?, Some("rbxmx") => snapshot_xml_model_file(file)?, Some("rbxm") => snapshot_binary_model_file(file)?, - Some(_) | None => return Ok(None), + Some("json") => { + let file_stem = file.path + .file_stem().expect("Could not extract file stem") + .to_str().expect("Could not convert path to UTF-8"); + + if file_stem.ends_with(".model") { + snapshot_json_model_file(file)? + } else { + None + } + }, + Some(_) | None => None, }; if let Some(snapshot) = maybe_snapshot.as_mut() { @@ -256,6 +276,8 @@ fn snapshot_imfs_file<'source>( if let Some(snapshot_name) = instance_name { snapshot.name = snapshot_name; } + } else { + info!("File generated no snapshot: {}", file.path.display()); } Ok(maybe_snapshot) @@ -402,6 +424,57 @@ struct LocalizationEntryJson { values: HashMap, } +fn snapshot_json_model_file<'source>( + file: &'source ImfsFile, +) -> SnapshotResult<'source> { + let contents = str::from_utf8(&file.contents) + .map_err(|inner| SnapshotError::Utf8Error { + inner, + path: file.path.to_owned(), + })?; + + let json_instance: JsonModelInstance = serde_json::from_str(contents) + .map_err(|inner| SnapshotError::JsonModelDecodeError { + inner, + path: file.path.to_owned(), + })?; + + let mut snapshot = json_instance.into_snapshot(); + snapshot.metadata.source_path = Some(file.path.to_owned()); + + Ok(Some(snapshot)) +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +struct JsonModelInstance { + name: String, + class_name: String, + + #[serde(default = "Vec::new", skip_serializing_if = "Vec::is_empty")] + children: Vec, + + #[serde(default = "HashMap::new", skip_serializing_if = "HashMap::is_empty")] + properties: HashMap, +} + +impl JsonModelInstance { + fn into_snapshot(mut self) -> RbxSnapshotInstance<'static> { + let children = self.children + .drain(..) + .map(JsonModelInstance::into_snapshot) + .collect(); + + RbxSnapshotInstance { + name: Cow::Owned(self.name), + class_name: Cow::Owned(self.class_name), + properties: self.properties, + children, + metadata: Default::default(), + } + } +} + fn snapshot_xml_model_file<'source>( file: &'source ImfsFile, ) -> SnapshotResult<'source> { diff --git a/test-projects/composing-models/src/Remotes.model.json b/test-projects/composing-models/src/Remotes.model.json new file mode 100644 index 00000000..67748fe8 --- /dev/null +++ b/test-projects/composing-models/src/Remotes.model.json @@ -0,0 +1,14 @@ +{ + "Name": "All my Remote Events", + "ClassName": "Folder", + "Children": [ + { + "Name": "SendMoney", + "ClassName": "RemoteEvent" + }, + { + "Name": "SendItems", + "ClassName": "RemoteEvent" + } + ] +} \ No newline at end of file