Skip to main content

yog_cli/design/
data.rs

1//! Yog Data text (the `yog-data` crate: JSON plus comments, trailing commas
2//! and unquoted keys) as `serde_json` values, for the readers here that
3//! only look things up.
4
5use serde_json::{Map, Value};
6
7/// `v` as a `serde_json` value; typed values become their string forms.
8pub fn to_json(v: &yog_data::Value) -> Value {
9    match v {
10        yog_data::Value::Null => Value::Null,
11        yog_data::Value::Bool(b) => Value::Bool(*b),
12        yog_data::Value::Int(n) => Value::from(*n),
13        yog_data::Value::Float(f) => serde_json::Number::from_f64(*f).map_or(Value::Null, Value::Number),
14        yog_data::Value::Str(s) => Value::String(s.clone()),
15        yog_data::Value::List(items) => Value::Array(items.iter().map(to_json).collect()),
16        yog_data::Value::Map(m) => Value::Object(m.iter().map(|(k, v)| (k.to_string(), to_json(v))).collect()),
17        typed => Value::String(yog_data::typed::typed_string(typed).unwrap_or_default()),
18    }
19}
20
21/// `v` as Yog Data (plain values: no references or other typed strings).
22pub fn from_json(v: &Value) -> yog_data::Value {
23    match v {
24        Value::Null => yog_data::Value::Null,
25        Value::Bool(b) => yog_data::Value::Bool(*b),
26        Value::Number(n) => n.as_i64().map_or_else(|| yog_data::Value::Float(n.as_f64().unwrap_or(0.0)), yog_data::Value::Int),
27        Value::String(s) => yog_data::Value::Str(s.clone()),
28        Value::Array(items) => yog_data::Value::List(items.iter().map(from_json).collect()),
29        Value::Object(m) => {
30            let mut out = yog_data::Map::new();
31            for (k, v) in m {
32                out.insert(k.as_str(), from_json(v));
33            }
34            yog_data::Value::Map(out)
35        }
36    }
37}
38
39/// A Yog Data file that's one object, `None` if it isn't.
40pub fn read_object(text: &str) -> Option<Map<String, Value>> {
41    match to_json(&yog_data::text::parse_value(text).ok()?) {
42        Value::Object(m) => Some(m),
43        _ => None,
44    }
45}
46
47/// `v` with its references as the runtime writes ids: `@item:x` → `x`,
48/// `@tag:item:x` → `#x` (what yog-book's renderer and the hosts read).
49pub fn runtime_ids(v: &mut Value) {
50    match v {
51        Value::String(s) if s.starts_with('@') => match yog_data::typed::classify(s) {
52            Ok(yog_data::Value::Ref(r)) => *s = r.id,
53            Ok(yog_data::Value::Tag(r)) => *s = format!("#{}", r.id),
54            Ok(yog_data::Value::Str(plain)) => *s = plain,
55            _ => {}
56        },
57        Value::Array(items) => items.iter_mut().for_each(runtime_ids),
58        Value::Object(m) => m.values_mut().for_each(runtime_ids),
59        _ => {}
60    }
61}
62
63/// The value at `keys` of `v`, one map key after another.
64pub fn at<'a>(v: &'a Value, keys: &[&str]) -> Option<&'a Value> {
65    keys.iter().try_fold(v, |v, k| v.get(*k))
66}