From 6e320b1fd563100623ea117f6014d1e8a4ee80c9 Mon Sep 17 00:00:00 2001 From: Shae Date: Fri, 14 Jul 2023 15:36:50 -0500 Subject: [PATCH] Add support for TOML files (#633) TOML maps well to Lua, is easier to read and write than JSON, and is commonly used by Roblox tools. Use cases: * Put game, plugin, or library config in a toml file * Sync in toml files generated by tools * Sync in config files for tools so that the game can double-check that the config file has been followed. (e.g. check that packages match versions specified in wally.toml) --- CHANGELOG.md | 2 + Cargo.lock | 1 + Cargo.toml | 1 + src/snapshot_middleware/mod.rs | 4 + ...leware__toml__test__instance_from_vfs.snap | 20 +++ src/snapshot_middleware/toml.rs | 123 ++++++++++++++++++ 6 files changed, 151 insertions(+) create mode 100644 src/snapshot_middleware/snapshots/librojo__snapshot_middleware__toml__test__instance_from_vfs.snap create mode 100644 src/snapshot_middleware/toml.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 130175e7..2c2e1bbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * Improve tooltip behavior ([#723]) * Better settings controls ([#725]) * Rework patch visualizer with many fixes and improvements ([#726]) +* Added support for syncing in `.toml` files ([#633]) [#668]: https://github.com/rojo-rbx/rojo/pull/668 [#674]: https://github.com/rojo-rbx/rojo/pull/674 @@ -32,6 +33,7 @@ [#723]: https://github.com/rojo-rbx/rojo/pull/723 [#725]: https://github.com/rojo-rbx/rojo/pull/725 [#726]: https://github.com/rojo-rbx/rojo/pull/726 +[#633]: https://github.com/rojo-rbx/rojo/pull/633 ## [7.3.0] - April 22, 2023 * Added `$attributes` to project format. ([#574]) diff --git a/Cargo.lock b/Cargo.lock index 12e9f860..fab34b07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1922,6 +1922,7 @@ dependencies = [ "termcolor", "thiserror", "tokio", + "toml", "tracy-client 0.13.2", "uuid", "walkdir", diff --git a/Cargo.toml b/Cargo.toml index bbbbcbf9..124d5c8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,6 +80,7 @@ ritz = "0.1.0" roblox_install = "1.0.0" serde = { version = "1.0.130", features = ["derive", "rc"] } serde_json = "1.0.68" +toml = "0.5.9" termcolor = "1.1.2" thiserror = "1.0.30" tokio = { version = "1.12.0", features = ["rt", "rt-multi-thread"] } diff --git a/src/snapshot_middleware/mod.rs b/src/snapshot_middleware/mod.rs index 3d5de76b..c690a223 100644 --- a/src/snapshot_middleware/mod.rs +++ b/src/snapshot_middleware/mod.rs @@ -14,6 +14,7 @@ mod meta_file; mod project; mod rbxm; mod rbxmx; +mod toml; mod txt; mod util; @@ -32,6 +33,7 @@ use self::{ project::snapshot_project, rbxm::snapshot_rbxm, rbxmx::snapshot_rbxmx, + toml::snapshot_toml, txt::snapshot_txt, util::PathExt, }; @@ -117,6 +119,8 @@ pub fn snapshot_from_vfs( return Ok(None); } else if path.file_name_ends_with(".json") { return snapshot_json(context, vfs, path); + } else if path.file_name_ends_with(".toml") { + return snapshot_toml(context, vfs, path); } else if let Ok(name) = csv_name { match name { // init csv are handled elsewhere and should not turn into diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__toml__test__instance_from_vfs.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__toml__test__instance_from_vfs.snap new file mode 100644 index 00000000..3529bf89 --- /dev/null +++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__toml__test__instance_from_vfs.snap @@ -0,0 +1,20 @@ +--- +source: src/snapshot_middleware/toml.rs +expression: instance_snapshot +--- +snapshot_id: "00000000000000000000000000000000" +metadata: + ignore_unknown_instances: false + instigating_source: + Path: /foo.toml + relevant_paths: + - /foo.toml + - /foo.meta.json + context: {} +name: foo +class_name: ModuleScript +properties: + Source: + String: "return {\n\t[\"1invalidident\"] = \"nice\",\n\tarray = {1, 2, 3},\n\tdates = {\n\t\tlocaldate = \"1979-05-27\",\n\t\tlocaldatetime = \"1979-05-27T07:32:00\",\n\t\tlocaltime = \"00:32:00.999999\",\n\t\toffset1 = \"1979-05-27T00:32:00.999999-07:00\",\n\t\toffset2 = \"1979-05-27T07:32:00Z\",\n\t},\n\t[\"false\"] = false,\n\tfloat = 1234.5452,\n\tint = 1234,\n\tobject = {\n\t\thello = \"world\",\n\t},\n\t[\"true\"] = true,\n}" +children: [] + diff --git a/src/snapshot_middleware/toml.rs b/src/snapshot_middleware/toml.rs new file mode 100644 index 00000000..6ee9be47 --- /dev/null +++ b/src/snapshot_middleware/toml.rs @@ -0,0 +1,123 @@ +use std::path::Path; + +use anyhow::Context; +use maplit::hashmap; +use memofs::{IoResultExt, Vfs}; + +use crate::{ + lua_ast::{Expression, Statement}, + snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot}, +}; + +use super::{meta_file::AdjacentMetadata, util::PathExt}; + +pub fn snapshot_toml( + context: &InstanceContext, + vfs: &Vfs, + path: &Path, +) -> anyhow::Result> { + let name = path.file_name_trim_end(".toml")?; + let contents = vfs.read(path)?; + + let value: toml::Value = toml::from_slice(&contents) + .with_context(|| format!("File contains malformed TOML: {}", path.display()))?; + + let as_lua = toml_to_lua(value).to_string(); + + let properties = hashmap! { + "Source".to_owned() => as_lua.into(), + }; + + let meta_path = path.with_file_name(format!("{}.meta.json", name)); + + 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 toml_to_lua(value: toml::Value) -> Statement { + Statement::Return(toml_to_lua_value(value)) +} + +fn toml_to_lua_value(value: toml::Value) -> Expression { + use toml::Value; + + match value { + Value::Datetime(value) => Expression::String(value.to_string()), + Value::Boolean(value) => Expression::Bool(value), + Value::Float(value) => Expression::Number(value), + Value::Integer(value) => Expression::Number(value as f64), + Value::String(value) => Expression::String(value), + Value::Array(values) => { + Expression::Array(values.into_iter().map(toml_to_lua_value).collect()) + } + Value::Table(values) => Expression::table( + values + .into_iter() + .map(|(key, value)| (key.into(), toml_to_lua_value(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.toml", + VfsSnapshot::file( + r#" + array = [1, 2, 3] + true = true + false = false + int = 1234 + float = 1234.5452 + "1invalidident" = "nice" + + [object] + hello = "world" + + [dates] + offset1 = 1979-05-27T00:32:00.999999-07:00 + offset2 = 1979-05-27 07:32:00Z + localdatetime = 1979-05-27T07:32:00 + localdate = 1979-05-27 + localtime = 00:32:00.999999 + "#, + ), + ) + .unwrap(); + + let mut vfs = Vfs::new(imfs.clone()); + + let instance_snapshot = snapshot_toml( + &InstanceContext::default(), + &mut vfs, + Path::new("/foo.toml"), + ) + .unwrap() + .unwrap(); + + insta::assert_yaml_snapshot!(instance_snapshot); + } +}