Skip to main content

microclaw_engine/internal/tool_runtime/
env_file.rs

1//! Dotenv file parsing utilities for skill environment injection.
2
3use std::collections::HashMap;
4
5/// Parse a dotenv-format string into a key-value map.
6///
7/// Supports:
8/// - `KEY=value`
9/// - `export KEY=value`
10/// - Quoted values (`"value"` or `'value'`)
11/// - Comments (`# ...`) and blank lines
12pub 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}