robinpath_modules/modules/
toml_mod.rs1use robinpath::{RobinPath, Value};
2
3pub fn register(rp: &mut RobinPath) {
4 rp.register_builtin("toml.parse", |args, _| {
5 let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
6 Ok(parse_toml(&s))
7 });
8
9 rp.register_builtin("toml.stringify", |args, _| {
10 let val = args.first().cloned().unwrap_or(Value::Null);
11 Ok(Value::String(stringify_toml(&val)))
12 });
13
14 rp.register_builtin("toml.parseFile", |args, _| {
15 let path = args.first().map(|v| v.to_display_string()).unwrap_or_default();
16 match std::fs::read_to_string(&path) {
17 Ok(content) => Ok(parse_toml(&content)),
18 Err(e) => Err(format!("toml.parseFile error: {}", e)),
19 }
20 });
21
22 rp.register_builtin("toml.writeFile", |args, _| {
23 let path = args.first().map(|v| v.to_display_string()).unwrap_or_default();
24 let val = args.get(1).cloned().unwrap_or(Value::Null);
25 let toml_str = stringify_toml(&val);
26 match std::fs::write(&path, &toml_str) {
27 Ok(()) => Ok(Value::Bool(true)),
28 Err(e) => Err(format!("toml.writeFile error: {}", e)),
29 }
30 });
31
32 rp.register_builtin("toml.get", |args, _| {
33 let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
34 let path = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
35 let parsed = parse_toml(&s);
36 Ok(get_by_path(&parsed, &path))
37 });
38
39 rp.register_builtin("toml.isValid", |args, _| {
40 let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
41 let parsed = parse_toml(&s);
42 Ok(Value::Bool(!matches!(parsed, Value::Null)))
43 });
44
45 rp.register_builtin("toml.toJSON", |args, _| {
46 let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
47 let parsed = parse_toml(&s);
48 let json_val: serde_json::Value = parsed.into();
49 Ok(Value::String(serde_json::to_string_pretty(&json_val).unwrap_or_default()))
50 });
51
52 rp.register_builtin("toml.fromJSON", |args, _| {
53 let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
54 match serde_json::from_str::<serde_json::Value>(&s) {
55 Ok(v) => Ok(Value::String(stringify_toml(&Value::from(v)))),
56 Err(e) => Err(format!("toml.fromJSON error: {}", e)),
57 }
58 });
59}
60
61fn parse_toml(s: &str) -> Value {
62 let mut result = indexmap::IndexMap::new();
63 let mut current_section = String::new();
64
65 for line in s.lines() {
66 let trimmed = line.trim();
67 if trimmed.is_empty() || trimmed.starts_with('#') {
68 continue;
69 }
70 if trimmed.starts_with('[') && trimmed.ends_with(']') && !trimmed.starts_with("[[") {
72 current_section = trimmed[1..trimmed.len() - 1].trim().to_string();
73 ensure_section(&mut result, ¤t_section);
74 continue;
75 }
76 if trimmed.starts_with("[[") && trimmed.ends_with("]]") {
78 current_section = trimmed[2..trimmed.len() - 2].trim().to_string();
79 continue;
81 }
82 if let Some(eq_pos) = trimmed.find('=') {
84 let key = trimmed[..eq_pos].trim().to_string();
85 let value_str = trimmed[eq_pos + 1..].trim();
86 let value = parse_toml_value(value_str);
87
88 if current_section.is_empty() {
89 result.insert(key, value);
90 } else {
91 set_in_section(&mut result, ¤t_section, key, value);
92 }
93 }
94 }
95 if result.is_empty() {
96 Value::Null
97 } else {
98 Value::Object(result)
99 }
100}
101
102fn parse_toml_value(s: &str) -> Value {
103 let trimmed = s.trim();
104 if (trimmed.starts_with('"') && trimmed.ends_with('"'))
106 || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
107 {
108 return Value::String(trimmed[1..trimmed.len() - 1].to_string());
109 }
110 if trimmed == "true" { return Value::Bool(true); }
112 if trimmed == "false" { return Value::Bool(false); }
113 if trimmed.starts_with('[') && trimmed.ends_with(']') {
115 let inner = &trimmed[1..trimmed.len() - 1];
116 let items: Vec<Value> = inner.split(',')
117 .map(|item| parse_toml_value(item.trim()))
118 .filter(|v| !matches!(v, Value::String(s) if s.is_empty()))
119 .collect();
120 return Value::Array(items);
121 }
122 if let Ok(n) = trimmed.parse::<f64>() {
124 return Value::Number(n);
125 }
126 let cleaned = trimmed.replace('_', "");
128 if let Ok(n) = cleaned.parse::<f64>() {
129 return Value::Number(n);
130 }
131 Value::String(trimmed.to_string())
132}
133
134fn ensure_section(result: &mut indexmap::IndexMap<String, Value>, section: &str) {
135 let parts: Vec<&str> = section.split('.').collect();
136 let mut current = result;
137 for part in parts {
138 if !current.contains_key(part) {
139 current.insert(part.to_string(), Value::Object(indexmap::IndexMap::new()));
140 }
141 if let Some(Value::Object(obj)) = current.get_mut(part) {
142 current = obj;
143 } else {
144 return;
145 }
146 }
147}
148
149fn set_in_section(result: &mut indexmap::IndexMap<String, Value>, section: &str, key: String, value: Value) {
150 let parts: Vec<&str> = section.split('.').collect();
151 let mut current = result;
152 for part in parts {
153 if let Some(Value::Object(obj)) = current.get_mut(part) {
154 current = obj;
155 } else {
156 return;
157 }
158 }
159 current.insert(key, value);
160}
161
162fn stringify_toml(val: &Value) -> String {
163 let mut lines = Vec::new();
164 if let Value::Object(obj) = val {
165 for (k, v) in obj {
167 if !matches!(v, Value::Object(_)) {
168 lines.push(format!("{} = {}", k, format_toml_value(v)));
169 }
170 }
171 if !lines.is_empty() {
172 lines.push(String::new());
173 }
174 for (k, v) in obj {
176 if let Value::Object(inner) = v {
177 lines.push(format!("[{}]", k));
178 for (ik, iv) in inner {
179 if let Value::Object(_) = iv {
180 lines.push(String::new());
182 lines.push(format!("[{}.{}]", k, ik));
183 stringify_toml_section(iv, &mut lines);
184 } else {
185 lines.push(format!("{} = {}", ik, format_toml_value(iv)));
186 }
187 }
188 lines.push(String::new());
189 }
190 }
191 }
192 lines.join("\n").trim_end().to_string()
193}
194
195fn stringify_toml_section(val: &Value, lines: &mut Vec<String>) {
196 if let Value::Object(obj) = val {
197 for (k, v) in obj {
198 lines.push(format!("{} = {}", k, format_toml_value(v)));
199 }
200 }
201}
202
203fn format_toml_value(val: &Value) -> String {
204 match val {
205 Value::String(s) => format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")),
206 Value::Number(n) => {
207 if *n == (*n as i64) as f64 { format!("{}", *n as i64) } else { format!("{}", n) }
208 }
209 Value::Bool(b) => if *b { "true" } else { "false" }.to_string(),
210 Value::Array(arr) => {
211 let items: Vec<String> = arr.iter().map(format_toml_value).collect();
212 format!("[{}]", items.join(", "))
213 }
214 Value::Null => "\"\"".to_string(),
215 _ => format!("\"{}\"", val.to_display_string()),
216 }
217}
218
219fn get_by_path(val: &Value, path: &str) -> Value {
220 let parts: Vec<&str> = path.split('.').collect();
221 let mut current = val.clone();
222 for part in parts {
223 if let Value::Object(obj) = ¤t {
224 current = obj.get(part).cloned().unwrap_or(Value::Null);
225 } else {
226 return Value::Null;
227 }
228 }
229 current
230}