use std::collections::BTreeMap;
pub fn parse_dotenv(text: &str) -> BTreeMap<String, String> {
let mut out = BTreeMap::new();
for line in text.lines() {
let t = line.trim();
if t.is_empty() || t.starts_with('#') {
continue;
}
let Some(eq) = t.find('=') else { continue };
let key = t[..eq].trim().to_string();
let mut value = t[eq + 1..].trim().to_string();
let quoted = (value.starts_with('"') && value.ends_with('"') && value.len() >= 2)
|| (value.starts_with('\'') && value.ends_with('\'') && value.len() >= 2);
if quoted {
value = value[1..value.len() - 1].to_string();
}
out.insert(key, value);
}
out
}
pub fn render_dotenv(entries: &BTreeMap<String, String>) -> String {
let mut text = entries
.iter()
.map(|(k, v)| format!("{k}={}", serde_json::to_string(v).unwrap()))
.collect::<Vec<_>>()
.join("\n");
if !entries.is_empty() {
text.push('\n');
}
text
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_and_quotes() {
let parsed = parse_dotenv("# c\nA=1\nB=\"two words\"\nC='x'\nbad line\n");
assert_eq!(parsed.get("A").map(String::as_str), Some("1"));
assert_eq!(parsed.get("B").map(String::as_str), Some("two words"));
assert_eq!(parsed.get("C").map(String::as_str), Some("x"));
assert_eq!(parsed.len(), 3);
assert_eq!(
render_dotenv(&parsed),
"A=\"1\"\nB=\"two words\"\nC=\"x\"\n"
);
assert_eq!(render_dotenv(&BTreeMap::new()), "");
}
}