Skip to main content

robinpath_modules/modules/
ini_mod.rs

1use 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(&section) {
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(&section) {
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(&section);
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(&section) {
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            // Remove surrounding quotes
123            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                // Global section
130                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(&current_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}