robinpath_modules/modules/
ini_mod.rs1use robinpath::{RobinPath, Value};
2
3pub fn register(rp: &mut RobinPath) {
4 rp.register_builtin("ini.parse", |args, _| {
5 let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
6 Ok(Value::Object(parse_ini(&s)))
7 });
8
9 rp.register_builtin("ini.stringify", |args, _| {
10 let obj = args.first().cloned().unwrap_or(Value::Null);
11 if let Value::Object(map) = &obj {
12 Ok(Value::String(stringify_ini(map)))
13 } else {
14 Ok(Value::String(String::new()))
15 }
16 });
17
18 rp.register_builtin("ini.parseFile", |args, _| {
19 let path = args.first().map(|v| v.to_display_string()).unwrap_or_default();
20 match std::fs::read_to_string(&path) {
21 Ok(content) => Ok(Value::Object(parse_ini(&content))),
22 Err(e) => Err(format!("ini.parseFile error: {}", e)),
23 }
24 });
25
26 rp.register_builtin("ini.writeFile", |args, _| {
27 let path = args.first().map(|v| v.to_display_string()).unwrap_or_default();
28 let obj = args.get(1).cloned().unwrap_or(Value::Null);
29 if let Value::Object(map) = &obj {
30 match std::fs::write(&path, stringify_ini(map)) {
31 Ok(()) => Ok(Value::Bool(true)),
32 Err(e) => Err(format!("ini.writeFile error: {}", e)),
33 }
34 } else {
35 Err("ini.writeFile expects an object".to_string())
36 }
37 });
38
39 rp.register_builtin("ini.get", |args, _| {
40 let content = args.first().map(|v| v.to_display_string()).unwrap_or_default();
41 let section = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
42 let key = args.get(2).map(|v| v.to_display_string()).unwrap_or_default();
43 let parsed = parse_ini(&content);
44 if let Some(Value::Object(sec)) = parsed.get(§ion) {
45 Ok(sec.get(&key).cloned().unwrap_or(Value::Null))
46 } else {
47 Ok(Value::Null)
48 }
49 });
50
51 rp.register_builtin("ini.set", |args, _| {
52 let content = args.first().map(|v| v.to_display_string()).unwrap_or_default();
53 let section = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
54 let key = args.get(2).map(|v| v.to_display_string()).unwrap_or_default();
55 let value = args.get(3).map(|v| v.to_display_string()).unwrap_or_default();
56 let mut parsed = parse_ini(&content);
57 let sec = parsed.entry(section).or_insert_with(|| Value::Object(indexmap::IndexMap::new()));
58 if let Value::Object(obj) = sec {
59 obj.insert(key, Value::String(value));
60 }
61 Ok(Value::String(stringify_ini(&parsed)))
62 });
63
64 rp.register_builtin("ini.getSections", |args, _| {
65 let content = args.first().map(|v| v.to_display_string()).unwrap_or_default();
66 let parsed = parse_ini(&content);
67 let sections: Vec<Value> = parsed.keys().map(|k| Value::String(k.clone())).collect();
68 Ok(Value::Array(sections))
69 });
70
71 rp.register_builtin("ini.getKeys", |args, _| {
72 let content = args.first().map(|v| v.to_display_string()).unwrap_or_default();
73 let section = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
74 let parsed = parse_ini(&content);
75 if let Some(Value::Object(sec)) = parsed.get(§ion) {
76 let keys: Vec<Value> = sec.keys().map(|k| Value::String(k.clone())).collect();
77 Ok(Value::Array(keys))
78 } else {
79 Ok(Value::Array(vec![]))
80 }
81 });
82
83 rp.register_builtin("ini.removeSection", |args, _| {
84 let content = args.first().map(|v| v.to_display_string()).unwrap_or_default();
85 let section = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
86 let mut parsed = parse_ini(&content);
87 parsed.shift_remove(§ion);
88 Ok(Value::String(stringify_ini(&parsed)))
89 });
90
91 rp.register_builtin("ini.removeKey", |args, _| {
92 let content = args.first().map(|v| v.to_display_string()).unwrap_or_default();
93 let section = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
94 let key = args.get(2).map(|v| v.to_display_string()).unwrap_or_default();
95 let mut parsed = parse_ini(&content);
96 if let Some(Value::Object(sec)) = parsed.get_mut(§ion) {
97 sec.shift_remove(&key);
98 }
99 Ok(Value::String(stringify_ini(&parsed)))
100 });
101}
102
103fn parse_ini(content: &str) -> indexmap::IndexMap<String, Value> {
104 let mut result = indexmap::IndexMap::new();
105 let mut current_section = String::new();
106
107 for line in content.lines() {
108 let line = line.trim();
109 if line.is_empty() || line.starts_with(';') || line.starts_with('#') {
110 continue;
111 }
112 if line.starts_with('[') && line.ends_with(']') {
113 current_section = line[1..line.len() - 1].trim().to_string();
114 result
115 .entry(current_section.clone())
116 .or_insert_with(|| Value::Object(indexmap::IndexMap::new()));
117 continue;
118 }
119 if let Some(eq_pos) = line.find('=') {
120 let key = line[..eq_pos].trim().to_string();
121 let mut value = line[eq_pos + 1..].trim().to_string();
122 if (value.starts_with('"') && value.ends_with('"'))
124 || (value.starts_with('\'') && value.ends_with('\''))
125 {
126 value = value[1..value.len() - 1].to_string();
127 }
128 if current_section.is_empty() {
129 result
131 .entry("".to_string())
132 .or_insert_with(|| Value::Object(indexmap::IndexMap::new()));
133 if let Some(Value::Object(sec)) = result.get_mut("") {
134 sec.insert(key, Value::String(value));
135 }
136 } else if let Some(Value::Object(sec)) = result.get_mut(¤t_section) {
137 sec.insert(key, Value::String(value));
138 }
139 }
140 }
141 result
142}
143
144fn stringify_ini(map: &indexmap::IndexMap<String, Value>) -> String {
145 let mut lines = Vec::new();
146 for (section, value) in map {
147 if !section.is_empty() {
148 lines.push(format!("[{}]", section));
149 }
150 if let Value::Object(obj) = value {
151 for (k, v) in obj {
152 lines.push(format!("{}={}", k, v.to_display_string()));
153 }
154 }
155 lines.push(String::new());
156 }
157 lines.join("\n").trim_end().to_string()
158}