robinpath_modules/modules/
dotenv_mod.rs1use robinpath::{RobinPath, Value};
2use std::fs;
3
4pub fn register(rp: &mut RobinPath) {
5 rp.register_builtin("dotenv.parse", |args, _| {
6 let content = args.first().map(|v| v.to_display_string()).unwrap_or_default();
7 Ok(Value::Object(parse_dotenv(&content)))
8 });
9
10 rp.register_builtin("dotenv.stringify", |args, _| {
11 let obj = args.first().cloned().unwrap_or(Value::Null);
12 if let Value::Object(map) = &obj {
13 let lines: Vec<String> = map
14 .iter()
15 .map(|(k, v)| {
16 let val = v.to_display_string();
17 if val.contains(' ') || val.contains('"') || val.contains('\'') {
18 format!("{}=\"{}\"", k, val.replace('"', "\\\""))
19 } else {
20 format!("{}={}", k, val)
21 }
22 })
23 .collect();
24 Ok(Value::String(lines.join("\n")))
25 } else {
26 Ok(Value::String(String::new()))
27 }
28 });
29
30 rp.register_builtin("dotenv.load", |args, _| {
31 let path = args
32 .first()
33 .map(|v| v.to_display_string())
34 .unwrap_or_else(|| ".env".to_string());
35 match fs::read_to_string(&path) {
36 Ok(content) => {
37 let vars = parse_dotenv(&content);
38 for (key, value) in &vars {
39 if let Value::String(val) = value {
40 unsafe { std::env::set_var(key, val) };
42 }
43 }
44 Ok(Value::Bool(true))
45 }
46 Err(e) => Err(format!("dotenv.load error: {}", e)),
47 }
48 });
49
50 rp.register_builtin("dotenv.read", |args, _| {
51 let path = args
52 .first()
53 .map(|v| v.to_display_string())
54 .unwrap_or_else(|| ".env".to_string());
55 match fs::read_to_string(&path) {
56 Ok(content) => Ok(Value::Object(parse_dotenv(&content))),
57 Err(e) => Err(format!("dotenv.read error: {}", e)),
58 }
59 });
60
61 rp.register_builtin("dotenv.get", |args, _| {
62 let path = args.first().map(|v| v.to_display_string()).unwrap_or_default();
63 let key = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
64 match fs::read_to_string(&path) {
65 Ok(content) => {
66 let vars = parse_dotenv(&content);
67 Ok(vars.get(&key).cloned().unwrap_or(Value::Null))
68 }
69 Err(_) => Ok(Value::Null),
70 }
71 });
72
73 rp.register_builtin("dotenv.set", |args, _| {
74 let path = args.first().map(|v| v.to_display_string()).unwrap_or_default();
75 let key = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
76 let value = args.get(2).map(|v| v.to_display_string()).unwrap_or_default();
77 let content = fs::read_to_string(&path).unwrap_or_default();
78 let mut vars = parse_dotenv(&content);
79 vars.insert(key, Value::String(value));
80 let lines: Vec<String> = vars
81 .iter()
82 .map(|(k, v)| format!("{}={}", k, v.to_display_string()))
83 .collect();
84 match fs::write(&path, lines.join("\n")) {
85 Ok(()) => Ok(Value::Bool(true)),
86 Err(e) => Err(format!("dotenv.set error: {}", e)),
87 }
88 });
89
90 rp.register_builtin("dotenv.remove", |args, _| {
91 let path = args.first().map(|v| v.to_display_string()).unwrap_or_default();
92 let key = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
93 let content = fs::read_to_string(&path).unwrap_or_default();
94 let mut vars = parse_dotenv(&content);
95 vars.shift_remove(&key);
96 let lines: Vec<String> = vars
97 .iter()
98 .map(|(k, v)| format!("{}={}", k, v.to_display_string()))
99 .collect();
100 match fs::write(&path, lines.join("\n")) {
101 Ok(()) => Ok(Value::Bool(true)),
102 Err(e) => Err(format!("dotenv.remove error: {}", e)),
103 }
104 });
105}
106
107fn parse_dotenv(content: &str) -> indexmap::IndexMap<String, Value> {
108 let mut map = indexmap::IndexMap::new();
109 for line in content.lines() {
110 let line = line.trim();
111 if line.is_empty() || line.starts_with('#') {
113 continue;
114 }
115 if let Some(eq_pos) = line.find('=') {
116 let key = line[..eq_pos].trim().to_string();
117 let mut value = line[eq_pos + 1..].trim().to_string();
118 if (value.starts_with('"') && value.ends_with('"'))
120 || (value.starts_with('\'') && value.ends_with('\''))
121 {
122 value = value[1..value.len() - 1].to_string();
123 }
124 value = value.replace("\\n", "\n").replace("\\\"", "\"");
126 if !key.is_empty() {
127 map.insert(key, Value::String(value));
128 }
129 }
130 }
131 map
132}