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)
This commit is contained in:
Shae
2023-07-14 15:36:50 -05:00
committed by GitHub
parent 6e40993199
commit 6e320b1fd5
6 changed files with 151 additions and 0 deletions

View File

@@ -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])

1
Cargo.lock generated
View File

@@ -1922,6 +1922,7 @@ dependencies = [
"termcolor",
"thiserror",
"tokio",
"toml",
"tracy-client 0.13.2",
"uuid",
"walkdir",

View File

@@ -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"] }

View File

@@ -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

View File

@@ -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: []

View File

@@ -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<Option<InstanceSnapshot>> {
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);
}
}