Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add YAML/TOML parsers for Lua configuration API #4969

Merged
merged 5 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions ci/generate-docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,10 @@ def render(self, output, depth=0, mode="mdbook"):
"module: wezterm.procinfo",
"config/lua/wezterm.procinfo",
),
Gen(
"module: wezterm.serde",
"config/lua/wezterm.serde",
),
Gen(
"module: wezterm.time",
"config/lua/wezterm.time",
Expand Down
8 changes: 8 additions & 0 deletions docs/config/lua/wezterm.serde/index.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# `wezterm.serde` module

{{since('nightly')}}

The `wezterm.serde` module provides functions for parsing the given string as
`json`, `yaml`, or `toml`, returning the corresponding `Lua` values, and vice versa.

## Available functions
12 changes: 12 additions & 0 deletions docs/config/lua/wezterm.serde/json_decode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# `wezterm.serde.json_decode(string)`

{{since('nightly')}}

Parses the supplied string as `json` and returns the equivalent `lua` values:

```
> wezterm.serde.json_decode('{"foo":"bar"}')
{
"foo": "bar",
}
```
10 changes: 10 additions & 0 deletions docs/config/lua/wezterm.serde/json_encode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# `wezterm.serde.json_encode(value)`

{{since('nightly')}}

Encodes the supplied `lua` value as `json`:

```
> wezterm.serde.json_encode({foo = "bar"})
"{\"foo\":\"bar\"}"
```
10 changes: 10 additions & 0 deletions docs/config/lua/wezterm.serde/json_encode_pretty.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# `wezterm.serde.json_encode_pretty(value)`

{{since('nightly')}}

Encodes the supplied `lua` value as a pretty-printed string of `json`:

```
> wezterm.serde.json_encode_pretty({foo = "bar"})
"{\n \"foo\": \"bar\"\n}"
```
12 changes: 12 additions & 0 deletions docs/config/lua/wezterm.serde/toml_decode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# `wezterm.serde.toml_decode(string)`

{{since('nightly')}}

Parses the supplied string as `toml` and returns the equivalent `lua` values:

```
> wezterm.serde.toml_decode('foo = "bar"')
{
"foo": "bar",
}
```
10 changes: 10 additions & 0 deletions docs/config/lua/wezterm.serde/toml_encode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# `wezterm.serde.toml_encode(value)`

{{since('nightly')}}

Encodes the supplied `lua` value as `toml`:

```
> wezterm.serde.toml_encode({foo = { "bar", "baz", "qux" } })
"foo = [\"bar\", \"baz\", \"qux\"]\n"
```
10 changes: 10 additions & 0 deletions docs/config/lua/wezterm.serde/toml_encode_pretty.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# `wezterm.serde.toml_encode_pretty(value)`

{{since('nightly')}}

Encodes the supplied `lua` value as a pretty-printed string of `toml`:

```
> wezterm.serde.toml_encode_pretty({foo = { "bar", "baz", "qux" } })
"foo = [\n \"bar\",\n \"baz\",\n \"qux\",\n]\n"
```
12 changes: 12 additions & 0 deletions docs/config/lua/wezterm.serde/yaml_decode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# `wezterm.serde.yaml_decode(string)`

{{since('nightly')}}

Parses the supplied string as `yaml` and returns the equivalent `lua` values:

```
> wezterm.serde.yaml_decode('---\n# comment\nfoo: "bar"')
{
"foo": "bar",
}
```
10 changes: 10 additions & 0 deletions docs/config/lua/wezterm.serde/yaml_encode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# `wezterm.serde.yaml_encode(value)`

{{since('nightly')}}

Encodes the supplied `lua` value as `yaml`:

```
> wezterm.serde.yaml_encode({foo = "bar"})
"foo: bar\n"
```
2 changes: 1 addition & 1 deletion env-bootstrap/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ logging = { path = "../lua-api-crates/logging" }
mux-lua = { path = "../lua-api-crates/mux" }
procinfo-funcs = { path = "../lua-api-crates/procinfo-funcs" }
filesystem = { path = "../lua-api-crates/filesystem" }
json = { path = "../lua-api-crates/json" }
serde-funcs = { path = "../lua-api-crates/serde-funcs" }
plugin = { path = "../lua-api-crates/plugin" }
share-data = { path = "../lua-api-crates/share-data" }
ssh-funcs = { path = "../lua-api-crates/ssh-funcs" }
Expand Down
2 changes: 1 addition & 1 deletion env-bootstrap/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ fn register_lua_modules() {
mux_lua::register,
procinfo_funcs::register,
filesystem::register,
json::register,
serde_funcs::register,
plugin::register,
ssh_funcs::register,
spawn_funcs::register,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "json"
name = "serde-funcs"
version = "0.1.0"
edition = "2021"

Expand All @@ -11,3 +11,5 @@ config = { path = "../../config" }
luahelper = { path = "../../luahelper" }
wezterm-dynamic = { path = "../../wezterm-dynamic" }
serde_json = "1.0.82"
serde_yaml = "0.9.31"
toml = "0.8.9"
156 changes: 144 additions & 12 deletions lua-api-crates/json/src/lib.rs → lua-api-crates/serde-funcs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,85 @@
use config::lua::get_or_create_module;
use config::lua::mlua::{self, IntoLua, Lua, Value as LuaValue};
use config::lua::{get_or_create_module, get_or_create_sub_module};
use luahelper::lua_value_to_dynamic;
use serde_json::{Map, Value as JValue};
use std::collections::HashSet;
use wezterm_dynamic::{FromDynamic, Value as DynValue};

pub fn register(lua: &Lua) -> anyhow::Result<()> {
let serde_mod = get_or_create_sub_module(lua, "serde")?;

// Decoders:
serde_mod.set("json_decode", lua.create_function(json_decode)?)?;
serde_mod.set("yaml_decode", lua.create_function(yaml_decode)?)?;
serde_mod.set("toml_decode", lua.create_function(toml_decode)?)?;

// Encoders:
serde_mod.set("json_encode", lua.create_function(json_encode)?)?;
serde_mod.set("yaml_encode", lua.create_function(yaml_encode)?)?;
serde_mod.set("toml_encode", lua.create_function(toml_encode)?)?;
// Pretty ones:
serde_mod.set(
"json_encode_pretty",
lua.create_function(json_encode_pretty)?,
)?;
serde_mod.set(
"toml_encode_pretty",
lua.create_function(toml_encode_pretty)?,
)?;
// Note there is no pretty encoder for yaml, because the default one is pretty already.
// See https://github.com/dtolnay/serde-yaml/issues/226

// For backward compatibility.
let wezterm_mod = get_or_create_module(lua, "wezterm")?;
wezterm_mod.set("json_parse", lua.create_function(json_parse)?)?;
wezterm_mod.set("json_parse", lua.create_function(json_decode)?)?;
wezterm_mod.set("json_encode", lua.create_function(json_encode)?)?;

Ok(())
}

fn json_encode(_: &Lua, value: LuaValue) -> mlua::Result<String> {
let json = lua_value_to_json_value(value, &mut HashSet::new())?;
serde_json::to_string(&json).map_err(|err| mlua::Error::external(format!("{err:#}")))
}

fn json_encode_pretty(_: &Lua, value: LuaValue) -> mlua::Result<String> {
let json = lua_value_to_json_value(value, &mut HashSet::new())?;
serde_json::to_string_pretty(&json).map_err(|err| mlua::Error::external(format!("{err:#}")))
}

fn yaml_encode(_: &Lua, value: LuaValue) -> mlua::Result<String> {
let json = lua_value_to_json_value(value, &mut HashSet::new())?;
serde_yaml::to_string(&json).map_err(|err| mlua::Error::external(format!("{err:#}")))
}

fn toml_encode(_: &Lua, value: LuaValue) -> mlua::Result<String> {
let json = lua_value_to_json_value(value, &mut HashSet::new())?;
toml::to_string(&json).map_err(|err| mlua::Error::external(format!("{err:#}")))
}

fn toml_encode_pretty(_: &Lua, value: LuaValue) -> mlua::Result<String> {
let json = lua_value_to_json_value(value, &mut HashSet::new())?;
toml::to_string_pretty(&json).map_err(|err| mlua::Error::external(format!("{err:#}")))
}

fn json_decode(lua: &Lua, text: String) -> mlua::Result<LuaValue> {
let value =
serde_json::from_str(&text).map_err(|err| mlua::Error::external(format!("{err:#}")))?;
json_value_to_lua_value(lua, value)
}

fn yaml_decode(lua: &Lua, text: String) -> mlua::Result<LuaValue> {
let value: JValue =
serde_yaml::from_str(&text).map_err(|err| mlua::Error::external(format!("{err:#}")))?;
json_value_to_lua_value(lua, value)
}

fn toml_decode(lua: &Lua, text: String) -> mlua::Result<LuaValue> {
let value: JValue =
toml::from_str(&text).map_err(|err| mlua::Error::external(format!("{err:#}")))?;
json_value_to_lua_value(lua, value)
}

fn json_value_to_lua_value<'lua>(lua: &'lua Lua, value: JValue) -> mlua::Result<LuaValue> {
Ok(match value {
JValue::Null => LuaValue::Nil,
Expand Down Expand Up @@ -47,12 +115,6 @@ fn json_value_to_lua_value<'lua>(lua: &'lua Lua, value: JValue) -> mlua::Result<
})
}

fn json_parse<'lua>(lua: &'lua Lua, text: String) -> mlua::Result<LuaValue> {
let value =
serde_json::from_str(&text).map_err(|err| mlua::Error::external(format!("{err:#}")))?;
json_value_to_lua_value(lua, value)
}

fn dyn_to_json(value: DynValue) -> anyhow::Result<JValue> {
Ok(match value {
DynValue::Null => JValue::Null,
Expand Down Expand Up @@ -227,8 +289,78 @@ fn lua_value_to_json_value(value: LuaValue, visited: &mut HashSet<usize>) -> mlu
})
}

fn json_encode<'lua>(_: &'lua Lua, value: LuaValue) -> mlua::Result<String> {
let mut visited = HashSet::new();
let json = lua_value_to_json_value(value, &mut visited)?;
serde_json::to_string(&json).map_err(|err| mlua::Error::external(format!("{err:#}")))
#[cfg(test)]
mod test {
use super::*;
use serde_json::json;

#[test]
fn test_json_encode_decode() {
// We use the json Value from serde_json crate as input, and convert the LuaValue,
// propagate through the encode and decode processes, and convert it back to the json Value for result checking.
let j0 = json!({
"key2str": "value1", "key2int": 4, "key2float": 4.5,
"key2arr": vec![2, 3], "key2dict": {"a": "a_value", "b": 3}});

let lua = Lua::new();
let v0 = json_value_to_lua_value(&lua, j0.clone()).unwrap();
let s = json_encode(&lua, v0.clone()).unwrap();
let j1: JValue = serde_json::from_str(&s).unwrap();
assert_eq!(j0, j1);
let v1 = json_decode(&lua, s).unwrap();
let j1 = lua_value_to_json_value(v1, &mut HashSet::new()).unwrap();
assert_eq!(j0, j1);

// Do it again with the pretty variant.
let s = json_encode_pretty(&lua, v0.clone()).unwrap();
let j1: JValue = serde_json::from_str(&s).unwrap();
assert_eq!(j0, j1);
let v1 = json_decode(&lua, s).unwrap();
let j1 = lua_value_to_json_value(v1, &mut HashSet::new()).unwrap();
assert_eq!(j0, j1);
}

#[test]
fn test_yaml_encode_decode() {
// We use the json Value from serde_json crate as input, and convert the LuaValue,
// propagate through the encode and decode processes, and convert it back to the json Value for result checking.
let j0 = json!({
"key2str": "value1", "key2int": 4, "key2float": 4.5,
"key2arr": vec![2, 3], "key2dict": {"a": "a_value", "b": 3}});

let lua = Lua::new();
let v0 = json_value_to_lua_value(&lua, j0.clone()).unwrap();
let s = yaml_encode(&lua, v0.clone()).unwrap();
let j1: JValue = serde_yaml::from_str(&s).unwrap();
assert_eq!(j0, j1);
let v1 = yaml_decode(&lua, s).unwrap();
let j1 = lua_value_to_json_value(v1, &mut HashSet::new()).unwrap();
assert_eq!(j0, j1);
}

#[test]
fn test_toml_encode_decode() {
// We use the json Value from serde_json crate as input, and convert the LuaValue,
// propagate through the encode and decode processes, and convert it back to the json Value for result checking.
let j0 = json!({
"key2str": "value1", "key2int": 4, "key2float": 4.5,
"key2arr": vec![2, 3], "key2dict": {"a": "a_value", "b": 3}});

let lua = Lua::new();
let v0 = json_value_to_lua_value(&lua, j0.clone()).unwrap();
let s = toml_encode(&lua, v0.clone()).unwrap();
let j1: JValue = toml::from_str(&s).unwrap();
assert_eq!(j0, j1);
let v1 = toml_decode(&lua, s).unwrap();
let j1 = lua_value_to_json_value(v1, &mut HashSet::new()).unwrap();
assert_eq!(j0, j1);

// Do it again with the pretty variant.
let s = toml_encode_pretty(&lua, v0.clone()).unwrap();
let j1: JValue = toml::from_str(&s).unwrap();
assert_eq!(j0, j1);
let v1 = toml_decode(&lua, s).unwrap();
let j1 = lua_value_to_json_value(v1, &mut HashSet::new()).unwrap();
assert_eq!(j0, j1);
}
}
Loading