diff --git a/CHANGELOG.md b/CHANGELOG.md index 79abf800d..f63b87d7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,10 +53,13 @@ | `rbxm` | `.rbxm` | | `rbxmx` | `.rbxmx` | | `project` | `.project.json` | + | `yaml` | `.yml` | | `ignore` | None! | **All** sync rules are reset between project files, so they must be specified in each one when nesting them. This is to ensure that nothing can break other projects by changing how files are synced! +* Added the ability to sync `YAML` files into ModuleScripts in the same way that `TOML` and `JSON` files are synced. This will apply to `.yml` and `.yaml` files. ([#913]) + [#813]: https://github.com/rojo-rbx/rojo/pull/813 [#834]: https://github.com/rojo-rbx/rojo/pull/834 [#838]: https://github.com/rojo-rbx/rojo/pull/838 @@ -66,6 +69,7 @@ [#893]: https://github.com/rojo-rbx/rojo/pull/893 [#903]: https://github.com/rojo-rbx/rojo/pull/903 [#911]: https://github.com/rojo-rbx/rojo/pull/911 +[#913]: https://github.com/rojo-rbx/rojo/pull/913 [#915]: https://github.com/rojo-rbx/rojo/pull/915 ## [7.4.1] - February 20, 2024 diff --git a/Cargo.toml b/Cargo.toml index a35ccd942..d2a4dec17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,11 @@ rbx_reflection = "4.5.0" rbx_reflection_database = "0.2.10" rbx_xml = "0.13.3" +serde = { version = "1.0.197", features = ["derive", "rc"] } +serde_json = "1.0.114" +serde_yaml = "0.8.26" +toml = "0.5.11" + anyhow = "1.0.80" backtrace = "0.3.69" bincode = "1.3.3" @@ -81,9 +86,6 @@ reqwest = { version = "0.11.24", default-features = false, features = [ ] } ritz = "0.1.0" roblox_install = "1.0.0" -serde = { version = "1.0.197", features = ["derive", "rc"] } -serde_json = "1.0.114" -toml = "0.5.11" termcolor = "1.4.1" thiserror = "1.0.57" tokio = { version = "1.36.0", features = ["rt", "rt-multi-thread"] } @@ -111,6 +113,5 @@ criterion = "0.3.6" insta = { version = "1.36.1", features = ["redactions", "yaml"] } paste = "1.0.14" pretty_assertions = "1.4.0" -serde_yaml = "0.8.26" tempfile = "3.10.1" walkdir = "2.5.0" diff --git a/src/snapshot_middleware/mod.rs b/src/snapshot_middleware/mod.rs index 1c95154ef..d59ab2099 100644 --- a/src/snapshot_middleware/mod.rs +++ b/src/snapshot_middleware/mod.rs @@ -17,6 +17,7 @@ mod rbxmx; mod toml; mod txt; mod util; +mod yaml; use std::{ path::{Path, PathBuf}, @@ -41,6 +42,7 @@ use self::{ rbxmx::snapshot_rbxmx, toml::snapshot_toml, txt::snapshot_txt, + yaml::snapshot_yaml, }; pub use self::{project::snapshot_project_node, util::emit_legacy_scripts_default}; @@ -204,6 +206,7 @@ pub enum Middleware { Rbxmx, Toml, Text, + Yaml, Ignore, } @@ -229,6 +232,7 @@ impl Middleware { Self::Rbxmx => snapshot_rbxmx(context, vfs, path, name), Self::Toml => snapshot_toml(context, vfs, path, name), Self::Text => snapshot_txt(context, vfs, path, name), + Self::Yaml => snapshot_yaml(context, vfs, path, name), Self::Ignore => Ok(None), } } @@ -292,6 +296,7 @@ fn default_sync_rules() -> &'static [SyncRule] { sync_rule!("*.txt", Text), sync_rule!("*.rbxmx", Rbxmx), sync_rule!("*.rbxm", Rbxm), + sync_rule!("*.{yml,yaml}", Yaml), ] }) } diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__yaml__test__instance_from_vfs.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__yaml__test__instance_from_vfs.snap new file mode 100644 index 000000000..3e146d29d --- /dev/null +++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__yaml__test__instance_from_vfs.snap @@ -0,0 +1,20 @@ +--- +source: src/snapshot_middleware/yaml.rs +expression: instance_snapshot +--- +snapshot_id: "00000000000000000000000000000000" +metadata: + ignore_unknown_instances: false + instigating_source: + Path: /foo.yml + relevant_paths: + - /foo.yml + - /foo.meta.json + context: + emit_legacy_scripts: true +name: foo +class_name: ModuleScript +properties: + Source: + String: "return {\n\tstring = \"this is a string\",\n\tboolean = true,\n\tnumber = 1337,\n\t[\"value-with-hypen\"] = \"it sure is\",\n\tsequence = {\"wow\", 8675309},\n\tmap = {{\n\t\tkey = \"value\",\n\t}, {\n\t\tkey2 = \"value 2\",\n\t}, {\n\t\tkey3 = \"value 3\",\n\t}},\n\twhatever_this_is = {\"i imagine\", \"it's\", \"a\", \"sequence?\"},\n}" +children: [] diff --git a/src/snapshot_middleware/yaml.rs b/src/snapshot_middleware/yaml.rs new file mode 100644 index 000000000..3a5aa0c02 --- /dev/null +++ b/src/snapshot_middleware/yaml.rs @@ -0,0 +1,113 @@ +use std::{collections::HashMap, path::Path}; + +use anyhow::Context; +use memofs::{IoResultExt, Vfs}; + +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> { + let contents = vfs.read(path)?; + + let value: serde_yaml::Value = serde_yaml::from_slice(&contents) + .with_context(|| format!("File contains malformed YAML: {}", path.display()))?; + + let as_lua = yaml_to_lua(value).to_string(); + + let properties = HashMap::from_iter([("Source".to_owned(), as_lua.into())]); + + let meta_path = path.with_file_name(format!("{name}.meta.json")); + + let mut snapshot = InstanceSnapshot::new() + .name(name) + .class_name("ModuleScript") + .properties(properties) + .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_lua(value: serde_yaml::Value) -> Statement { + Statement::Return(yaml_to_lua_value(value)) +} + +fn yaml_to_lua_value(value: serde_yaml::Value) -> Expression { + use serde_yaml::Value; + + match value { + Value::Bool(value) => Expression::Bool(value), + Value::Null => Expression::Nil, + Value::Number(value) => Expression::Number(value.as_f64().unwrap()), + Value::String(value) => Expression::String(value), + Value::Mapping(map) => Expression::table( + map.into_iter() + .map(|(key, value)| (yaml_to_lua_value(key), yaml_to_lua_value(value))) + .collect(), + ), + Value::Sequence(seq) => Expression::Array(seq.into_iter().map(yaml_to_lua_value).collect()), + } +} + +#[cfg(test)] +mod test { + use super::*; + + use memofs::{InMemoryFs, VfsSnapshot}; + + #[test] + fn instance_from_vfs() { + let mut imfs = InMemoryFs::new(); + imfs.load_snapshot( + "/foo.yml", + VfsSnapshot::file( + r#" +--- +string: this is a string +boolean: true +number: 1337 +value-with-hypen: it sure is +sequence: + - wow + - 8675309 +map: + - key: value + - key2: "value 2" + - key3: 'value 3' +whatever_this_is: [i imagine, it's, a, sequence?]"#, + ), + ) + .unwrap(); + + let vfs = Vfs::new(imfs.clone()); + + let instance_snapshot = snapshot_yaml( + &InstanceContext::default(), + &vfs, + Path::new("/foo.yml"), + "foo", + ) + .unwrap() + .unwrap(); + + insta::assert_yaml_snapshot!(instance_snapshot); + } +}