microclaw_engine/internal/tool_runtime/
env_file.rs1use std::collections::HashMap;
4
5pub fn parse_dotenv(content: &str) -> HashMap<String, String> {
13 let mut map = HashMap::new();
14 for line in content.lines() {
15 let trimmed = line.trim();
16 if trimmed.is_empty() || trimmed.starts_with('#') {
17 continue;
18 }
19 if let Some(eq_pos) = trimmed.find('=') {
20 let key = trimmed[..eq_pos].trim();
21 if key.is_empty() {
22 continue;
23 }
24 let actual_key = key.strip_prefix("export ").map(str::trim).unwrap_or(key);
25 if actual_key.is_empty() {
26 continue;
27 }
28 let val = unquote_env_value(trimmed[eq_pos + 1..].trim());
29 map.insert(actual_key.to_string(), val);
30 }
31 }
32 map
33}
34
35fn unquote_env_value(s: &str) -> String {
36 if s.len() >= 2
37 && ((s.starts_with('"') && s.ends_with('"')) || (s.starts_with('\'') && s.ends_with('\'')))
38 {
39 return s[1..s.len() - 1].to_string();
40 }
41 s.to_string()
42}
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47
48 #[test]
49 fn test_parse_dotenv_basic() {
50 let content = "KEY1=value1\nKEY2=value2\n# comment\n\nKEY3=\"quoted value\"";
51 let map = parse_dotenv(content);
52 assert_eq!(map.get("KEY1").unwrap(), "value1");
53 assert_eq!(map.get("KEY2").unwrap(), "value2");
54 assert_eq!(map.get("KEY3").unwrap(), "quoted value");
55 assert_eq!(map.len(), 3);
56 }
57
58 #[test]
59 fn test_parse_dotenv_export_prefix() {
60 let content = "export API_KEY=secret123\nexport BASE_URL='https://example.com'";
61 let map = parse_dotenv(content);
62 assert_eq!(map.get("API_KEY").unwrap(), "secret123");
63 assert_eq!(map.get("BASE_URL").unwrap(), "https://example.com");
64 }
65
66 #[test]
67 fn test_parse_dotenv_empty_and_comments() {
68 let content = "# full comment\n\n \n";
69 let map = parse_dotenv(content);
70 assert!(map.is_empty());
71 }
72}