Skip to main content

supercode_interchange/world/codec/
dotenv.rs

1//! `.env` in a profile folder is the vault's store: `KEY=value`, quotes
2//! stripped on read, JSON-quoted on write, sorted by key.
3
4use std::collections::BTreeMap;
5
6/// Parse a `.env` text into its entries.
7pub fn parse_dotenv(text: &str) -> BTreeMap<String, String> {
8    let mut out = BTreeMap::new();
9    for line in text.lines() {
10        let t = line.trim();
11        if t.is_empty() || t.starts_with('#') {
12            continue;
13        }
14        let Some(eq) = t.find('=') else { continue };
15        let key = t[..eq].trim().to_string();
16        let mut value = t[eq + 1..].trim().to_string();
17        let quoted = (value.starts_with('"') && value.ends_with('"') && value.len() >= 2)
18            || (value.starts_with('\'') && value.ends_with('\'') && value.len() >= 2);
19        if quoted {
20            value = value[1..value.len() - 1].to_string();
21        }
22        out.insert(key, value);
23    }
24    out
25}
26
27/// Render entries as `.env` text, sorted by key, values JSON-quoted.
28pub fn render_dotenv(entries: &BTreeMap<String, String>) -> String {
29    let mut text = entries
30        .iter()
31        .map(|(k, v)| format!("{k}={}", serde_json::to_string(v).unwrap()))
32        .collect::<Vec<_>>()
33        .join("\n");
34    if !entries.is_empty() {
35        text.push('\n');
36    }
37    text
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43
44    #[test]
45    fn round_trip_and_quotes() {
46        let parsed = parse_dotenv("# c\nA=1\nB=\"two words\"\nC='x'\nbad line\n");
47        assert_eq!(parsed.get("A").map(String::as_str), Some("1"));
48        assert_eq!(parsed.get("B").map(String::as_str), Some("two words"));
49        assert_eq!(parsed.get("C").map(String::as_str), Some("x"));
50        assert_eq!(parsed.len(), 3);
51        assert_eq!(
52            render_dotenv(&parsed),
53            "A=\"1\"\nB=\"two words\"\nC=\"x\"\n"
54        );
55        assert_eq!(render_dotenv(&BTreeMap::new()), "");
56    }
57}