Skip to main content

robinpath_modules/modules/
yaml_mod.rs

1use robinpath::{RobinPath, Value};
2use crate::serde_convert::value_to_json;
3
4pub fn register(rp: &mut RobinPath) {
5    rp.register_builtin("yaml.parse", |args, _| {
6        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
7        Ok(parse_yaml(&s))
8    });
9
10    rp.register_builtin("yaml.stringify", |args, _| {
11        let val = args.first().cloned().unwrap_or(Value::Null);
12        let _indent = args.get(1).map(|v| v.to_number() as usize).unwrap_or(2);
13        Ok(Value::String(stringify_yaml(&val)))
14    });
15
16    rp.register_builtin("yaml.parseFile", |args, _| {
17        let path = args.first().map(|v| v.to_display_string()).unwrap_or_default();
18        match std::fs::read_to_string(&path) {
19            Ok(content) => Ok(parse_yaml(&content)),
20            Err(e) => Err(format!("yaml.parseFile error: {}", e)),
21        }
22    });
23
24    rp.register_builtin("yaml.writeFile", |args, _| {
25        let path = args.first().map(|v| v.to_display_string()).unwrap_or_default();
26        let val = args.get(1).cloned().unwrap_or(Value::Null);
27        let yaml_str = stringify_yaml(&val);
28        match std::fs::write(&path, &yaml_str) {
29            Ok(()) => Ok(Value::Bool(true)),
30            Err(e) => Err(format!("yaml.writeFile error: {}", e)),
31        }
32    });
33
34    rp.register_builtin("yaml.isValid", |args, _| {
35        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
36        let trimmed = s.trim();
37        if trimmed.is_empty() {
38            return Ok(Value::Bool(false));
39        }
40        // serde_yaml accepts any scalar as valid YAML, so check parse success
41        let is_valid = serde_yaml::from_str::<serde_yaml::Value>(trimmed).is_ok();
42        Ok(Value::Bool(is_valid))
43    });
44
45    rp.register_builtin("yaml.get", |args, _| {
46        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
47        let path = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
48        let parsed = parse_yaml(&s);
49        Ok(get_by_path(&parsed, &path))
50    });
51
52    rp.register_builtin("yaml.toJSON", |args, _| {
53        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
54        let parsed = parse_yaml(&s);
55        let json_val: serde_json::Value = parsed.into();
56        Ok(Value::String(serde_json::to_string_pretty(&json_val).unwrap_or_default()))
57    });
58
59    rp.register_builtin("yaml.fromJSON", |args, _| {
60        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
61        match serde_json::from_str::<serde_json::Value>(&s) {
62            Ok(v) => {
63                let val = Value::from(v);
64                Ok(Value::String(stringify_yaml(&val)))
65            }
66            Err(e) => Err(format!("yaml.fromJSON error: {}", e)),
67        }
68    });
69
70    rp.register_builtin("yaml.parseAll", |args, _| {
71        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
72        let docs: Vec<Value> = serde_yaml::Deserializer::from_str(&s)
73            .map(|doc| {
74                match serde_yaml::Value::deserialize(doc) {
75                    Ok(v) => yaml_value_to_value(v),
76                    Err(_) => Value::Null,
77                }
78            })
79            .collect();
80        Ok(Value::Array(docs))
81    });
82}
83
84use serde::Deserialize;
85
86fn parse_yaml(s: &str) -> Value {
87    let trimmed = s.trim();
88    if trimmed.is_empty() {
89        return Value::Null;
90    }
91
92    match serde_yaml::from_str::<serde_yaml::Value>(trimmed) {
93        Ok(v) => yaml_value_to_value(v),
94        Err(_) => Value::Null,
95    }
96}
97
98fn yaml_value_to_value(v: serde_yaml::Value) -> Value {
99    match v {
100        serde_yaml::Value::Null => Value::Null,
101        serde_yaml::Value::Bool(b) => Value::Bool(b),
102        serde_yaml::Value::Number(n) => {
103            if let Some(i) = n.as_i64() {
104                Value::Number(i as f64)
105            } else if let Some(f) = n.as_f64() {
106                Value::Number(f)
107            } else {
108                Value::Number(0.0)
109            }
110        }
111        serde_yaml::Value::String(s) => Value::String(s),
112        serde_yaml::Value::Sequence(arr) => {
113            Value::Array(arr.into_iter().map(yaml_value_to_value).collect())
114        }
115        serde_yaml::Value::Mapping(map) => {
116            let mut obj = indexmap::IndexMap::new();
117            for (k, v) in map {
118                let key = match k {
119                    serde_yaml::Value::String(s) => s,
120                    serde_yaml::Value::Number(n) => n.to_string(),
121                    serde_yaml::Value::Bool(b) => b.to_string(),
122                    _ => continue,
123                };
124                obj.insert(key, yaml_value_to_value(v));
125            }
126            Value::Object(obj)
127        }
128        serde_yaml::Value::Tagged(tagged) => yaml_value_to_value(tagged.value),
129    }
130}
131
132fn stringify_yaml(val: &Value) -> String {
133    let json = value_to_json(val);
134    // Convert through serde_yaml for proper YAML output
135    match serde_yaml::to_string(&json) {
136        Ok(s) => s.trim_end().to_string(),
137        Err(_) => String::new(),
138    }
139}
140
141fn get_by_path(val: &Value, path: &str) -> Value {
142    let parts: Vec<&str> = path.split('.').collect();
143    let mut current = val.clone();
144    for part in parts {
145        if let Value::Object(obj) = &current {
146            current = obj.get(part).cloned().unwrap_or(Value::Null);
147        } else {
148            return Value::Null;
149        }
150    }
151    current
152}