157 lines
4.6 KiB
Rust
157 lines
4.6 KiB
Rust
use kdl::{KdlDocument, KdlEntry, KdlNode, KdlValue, NodeKey};
|
|
use miette::{miette, IntoDiagnostic, Result};
|
|
use serde_json::Value;
|
|
use std::{env, fs};
|
|
|
|
fn main() -> Result<()> {
|
|
// Get paths for input and output files from first 2 passed arguments
|
|
let input = env::args()
|
|
.nth(1)
|
|
.ok_or_else(|| miette!("Please provide an input JSON file path"))?;
|
|
|
|
let output = env::args()
|
|
.nth(2)
|
|
.ok_or_else(|| miette!("Please provide an output KDL file path"))?;
|
|
|
|
// Read input file to string
|
|
let input = fs::read_to_string(&input)
|
|
.into_diagnostic()
|
|
.map_err(|err| err.context(format!("Could not read file `{input}`")))?;
|
|
|
|
// Parse input file into JSON
|
|
let input: Value = serde_json::from_str(&input).into_diagnostic()?;
|
|
|
|
// Parse JSON to KDL and write to output file
|
|
fs::write(output, json_to_kdl(input)?.to_string()).into_diagnostic()?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn json_to_kdl(json: Value) -> Result<KdlDocument> {
|
|
let nodes: Vec<Result<KdlNode>> = json
|
|
.as_array()
|
|
.ok_or_else(|| miette!("Document root must be a JSON array"))?
|
|
.iter()
|
|
.map(|value| {
|
|
let mut node = KdlNode::new(
|
|
value
|
|
.get("identifier")
|
|
.and_then(|ident| ident.as_str())
|
|
.ok_or_else(|| miette!("`identifier` must exist and be a String"))?,
|
|
);
|
|
|
|
if let Some(arguments) = value.get("arguments") {
|
|
let args: Vec<KdlValue> = arguments
|
|
.as_array()
|
|
.ok_or_else(|| miette!("`arguments` must be an Array"))?
|
|
.iter()
|
|
.filter_map(|v| value_to_kdl(v.to_owned()).ok())
|
|
.collect();
|
|
|
|
for arg in args {
|
|
node.push(KdlEntry::new(arg));
|
|
}
|
|
};
|
|
|
|
if let Some(properties) = value.get("properties") {
|
|
let properties: Vec<(NodeKey, KdlEntry)> = properties
|
|
.as_object()
|
|
.ok_or_else(|| miette!("`properties` must be an Object"))?
|
|
.iter()
|
|
.filter_map(|(key, value)| match value_to_kdl(value.to_owned()) {
|
|
Ok(val) => Some((key.to_owned().into(), KdlEntry::new(val))),
|
|
Err(_) => None,
|
|
})
|
|
.collect();
|
|
|
|
for (key, value) in properties {
|
|
node.insert(key, value);
|
|
}
|
|
};
|
|
|
|
if let Some(children) = value.get("children") {
|
|
node.set_children(json_to_kdl(children.to_owned())?);
|
|
};
|
|
|
|
Ok(node)
|
|
})
|
|
.collect();
|
|
|
|
let mut document = KdlDocument::new();
|
|
|
|
for node in nodes {
|
|
document.nodes_mut().push(node?);
|
|
}
|
|
|
|
Ok(document)
|
|
}
|
|
|
|
/// Try converting a JSON Value into a KDL Value
|
|
fn value_to_kdl(value: Value) -> Result<KdlValue> {
|
|
match value {
|
|
Value::Null => Ok(KdlValue::Null),
|
|
Value::Bool(bool) => Ok(KdlValue::Bool(bool)),
|
|
Value::Number(num) => {
|
|
if num.is_f64() {
|
|
Ok(KdlValue::Base10Float(num.as_f64().ok_or_else(|| {
|
|
miette!("{num} cannot be parsed into a float")
|
|
})?))
|
|
} else {
|
|
Ok(KdlValue::Base10(num.as_i64().ok_or_else(|| {
|
|
miette!("{num} cannot be parsed into a number")
|
|
})?))
|
|
}
|
|
}
|
|
Value::String(string) => Ok(KdlValue::String(string)),
|
|
_ => Err(miette!("Type cannot be represented as a KDL value")),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_conversion() -> Result<()> {
|
|
let input = serde_json::json!(
|
|
[
|
|
{
|
|
"identifier": "bees",
|
|
"arguments": [
|
|
true,
|
|
42,
|
|
3.1415,
|
|
null,
|
|
"how many eggs are you currently holding?"
|
|
],
|
|
"properties": {
|
|
"how many": "uhhh like 40?",
|
|
"state?": "quite upset"
|
|
}
|
|
},
|
|
{
|
|
"identifier": "lemon",
|
|
"children": [
|
|
{
|
|
"identifier": "child",
|
|
"properties": {
|
|
"age": 3
|
|
}
|
|
},
|
|
{
|
|
"identifier": "child-eater",
|
|
"arguments": [
|
|
":^)"
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"identifier": "ohno"
|
|
}
|
|
]);
|
|
|
|
assert_eq!(
|
|
json_to_kdl(input)?.to_string(),
|
|
"bees true 42 3.1415 null \"how many eggs are you currently holding?\" \"how many\"=\"uhhh like 40?\" state?=\"quite upset\"\nlemon {\n child age=3\n child-eater \":^)\"\n}\nohno\n"
|
|
);
|
|
|
|
Ok(())
|
|
}
|