1use anyhow::Context;
2use config::{
3 any_err, get_or_create_module, get_or_create_sub_module, materialize_to_lua_value,
4 serialize_options,
5};
6use mlua::{Lua, LuaSerdeExt, Value as LuaValue};
7use serde_json::Value as JValue;
8
9pub fn register(lua: &Lua) -> anyhow::Result<()> {
10 let serde_mod = get_or_create_sub_module(lua, "serde")?;
11 let kumo_mod = get_or_create_module(lua, "kumo")?;
12
13 serde_mod.set("json_load", lua.create_async_function(json_load)?)?;
14 serde_mod.set("json_parse", lua.create_function(json_parse)?)?;
15 serde_mod.set("json_encode", lua.create_function(json_encode)?)?;
16 serde_mod.set(
17 "json_encode_pretty",
18 lua.create_function(json_encode_pretty)?,
19 )?;
20
21 serde_mod.set("toml_load", lua.create_async_function(toml_load)?)?;
22 serde_mod.set("toml_parse", lua.create_function(toml_parse)?)?;
23 serde_mod.set("toml_encode", lua.create_function(toml_encode)?)?;
24 serde_mod.set(
25 "toml_encode_pretty",
26 lua.create_function(toml_encode_pretty)?,
27 )?;
28 serde_mod.set(
29 "toml_encode_pretty_compact",
30 lua.create_function(toml_encode_pretty_compact_lua)?,
31 )?;
32
33 serde_mod.set("yaml_load", lua.create_async_function(yaml_load)?)?;
34 serde_mod.set("yaml_parse", lua.create_function(yaml_parse)?)?;
35 serde_mod.set("yaml_encode", lua.create_function(yaml_encode)?)?;
36 kumo_mod.set("json_load", lua.create_async_function(json_load)?)?;
41 kumo_mod.set("json_parse", lua.create_function(json_parse)?)?;
42 kumo_mod.set("json_encode", lua.create_function(json_encode)?)?;
43 kumo_mod.set(
44 "json_encode_pretty",
45 lua.create_function(json_encode_pretty)?,
46 )?;
47
48 kumo_mod.set("toml_load", lua.create_async_function(toml_load)?)?;
49 kumo_mod.set("toml_parse", lua.create_function(toml_parse)?)?;
50 kumo_mod.set("toml_encode", lua.create_function(toml_encode)?)?;
51 kumo_mod.set(
52 "toml_encode_pretty",
53 lua.create_function(toml_encode_pretty)?,
54 )?;
55
56 Ok(())
57}
58
59async fn json_load(lua: Lua, file_name: String) -> mlua::Result<LuaValue> {
60 let data = tokio::fs::read(&file_name)
61 .await
62 .with_context(|| format!("reading file {file_name}"))
63 .map_err(any_err)?;
64
65 let stripped = json_comments::StripComments::new(&*data);
66
67 let obj: serde_json::Value = serde_json::from_reader(stripped)
68 .with_context(|| format!("parsing {file_name} as json"))
69 .map_err(any_err)?;
70 lua.to_value_with(&obj, serialize_options())
71}
72
73fn json_parse(lua: &Lua, text: String) -> mlua::Result<LuaValue> {
74 let stripped = json_comments::StripComments::new(text.as_bytes());
75 let obj: serde_json::Value = serde_json::from_reader(stripped)
76 .with_context(|| format!("parsing {text} as json"))
77 .map_err(any_err)?;
78 lua.to_value_with(&obj, serialize_options())
79}
80
81fn json_encode(lua: &Lua, value: LuaValue) -> mlua::Result<String> {
82 let value = materialize_to_lua_value(lua, value)?;
83 serde_json::to_string(&value).map_err(any_err)
84}
85
86fn json_encode_pretty(lua: &Lua, value: LuaValue) -> mlua::Result<String> {
87 let value = materialize_to_lua_value(lua, value)?;
88 let value = serde_json::to_value(&value).map_err(any_err)?;
94 serde_json::to_string_pretty(&value).map_err(any_err)
95}
96
97async fn toml_load(lua: Lua, file_name: String) -> mlua::Result<LuaValue> {
98 let data = tokio::fs::read_to_string(&file_name)
99 .await
100 .with_context(|| format!("reading file {file_name}"))
101 .map_err(any_err)?;
102
103 let obj: toml::Value = toml::from_str(&data)
104 .with_context(|| format!("parsing {file_name} as toml"))
105 .map_err(any_err)?;
106 lua.to_value_with(&obj, serialize_options())
107}
108
109fn toml_parse(lua: &Lua, toml: String) -> mlua::Result<LuaValue> {
110 let obj: toml::Value = toml::from_str(&toml)
111 .with_context(|| format!("parsing {toml} as toml"))
112 .map_err(any_err)?;
113 lua.to_value_with(&obj, serialize_options())
114}
115
116fn toml_encode(lua: &Lua, value: LuaValue) -> mlua::Result<String> {
117 let value = materialize_to_lua_value(lua, value)?;
118 toml::to_string(&value).map_err(any_err)
119}
120
121fn toml_encode_pretty(lua: &Lua, value: LuaValue) -> mlua::Result<String> {
122 let value = materialize_to_lua_value(lua, value)?;
123 toml::to_string_pretty(&value).map_err(any_err)
124}
125
126pub fn toml_encode_pretty_compact<T: serde::Serialize>(value: &T) -> anyhow::Result<String> {
137 let initial = toml::to_string(value).context("serializing value as TOML")?;
138 let mut doc: toml_edit::DocumentMut = initial
139 .parse()
140 .context("re-parsing TOML for format normalization")?;
141 fn normalize(table: &mut toml_edit::Table) {
142 let empty_keys: Vec<String> = table
148 .iter()
149 .filter_map(|(k, v)| match v {
150 toml_edit::Item::Table(t) if t.is_empty() => Some(k.to_string()),
151 _ => None,
152 })
153 .collect();
154 for key in empty_keys {
155 table.insert(
156 &key,
157 toml_edit::Item::Value(
158 toml_edit::Value::InlineTable(toml_edit::InlineTable::new()),
159 ),
160 );
161 }
162
163 for (_, item) in table.iter_mut() {
166 match item {
167 toml_edit::Item::Table(t) => normalize(t),
168 toml_edit::Item::ArrayOfTables(arr) => {
169 for t in arr.iter_mut() {
170 normalize(t);
171 }
172 }
173 _ => {}
174 }
175 }
176
177 table.sort_values();
181 }
182 normalize(doc.as_table_mut());
183 Ok(doc.to_string())
184}
185
186fn toml_encode_pretty_compact_lua(lua: &Lua, value: LuaValue) -> mlua::Result<String> {
187 let value = materialize_to_lua_value(lua, value)?;
188 toml_encode_pretty_compact(&value).map_err(any_err)
189}
190
191async fn yaml_load(lua: Lua, file_name: String) -> mlua::Result<LuaValue> {
192 let data = tokio::fs::read(&file_name)
193 .await
194 .with_context(|| format!("reading file {file_name}"))
195 .map_err(any_err)?;
196
197 let value: JValue = serde_yaml::from_slice(&data)
198 .with_context(|| format!("parsing {file_name} as yaml"))
199 .map_err(any_err)?;
200 lua.to_value_with(&value, serialize_options())
201}
202
203fn yaml_parse(lua: &Lua, text: String) -> mlua::Result<LuaValue> {
204 let value: JValue = serde_yaml::from_str(&text)
205 .with_context(|| format!("parsing {text} as yaml"))
206 .map_err(any_err)?;
207 lua.to_value_with(&value, serialize_options())
208}
209
210fn yaml_encode(lua: &Lua, value: LuaValue) -> mlua::Result<String> {
211 let value = materialize_to_lua_value(lua, value)?;
212 serde_yaml::to_string(&value).map_err(any_err)
213}