1use crate::parse::*;
2use std::collections::HashMap;
3
4pub struct Dotenv {
6 buf: String,
7}
8
9fn normalize_input(buf: String) -> String {
10 let buf = if let Some(stripped) = buf.strip_prefix('\u{FEFF}') {
12 stripped.to_string()
13 } else {
14 buf
15 };
16
17 buf.replace("\r\n", "\n").replace('\r', "\n")
19}
20
21impl Dotenv {
22 pub(crate) fn new(buf: String) -> Self {
23 Self {
24 buf: normalize_input(buf),
25 }
26 }
27
28 pub fn iter(&self) -> Iter<'_> {
30 Iter::new(&self.buf)
31 }
32
33 pub fn load(self) {
37 self.set_vars(false)
38 }
39
40 pub fn load_override(self) {
44 self.set_vars(true)
45 }
46
47 fn set_vars(self, override_env: bool) {
48 for (key, value) in self.iter() {
49 if override_env || std::env::var(key).is_err() {
50 std::env::set_var(key, value);
51 }
52 }
53 }
54}
55
56pub struct Iter<'a> {
58 resolved: HashMap<&'a str, String>,
59 input: &'a str,
60}
61
62impl<'a> Iter<'a> {
63 pub(crate) fn new(input: &'a str) -> Self {
64 Self {
65 resolved: HashMap::new(),
66 input,
67 }
68 }
69
70 fn resolve_var(&self, name: &'a str) -> Option<String> {
72 std::env::var(name)
73 .ok()
74 .or_else(|| self.resolved.get(name).cloned())
75 .filter(|it| !it.is_empty())
76 }
77
78 fn resolve(&self, value: Value<'a>) -> String {
79 match value {
80 Value::Lit(text) => text.to_string(),
81 Value::Sub(name, fallback) => self
82 .resolve_var(name)
83 .or_else(|| fallback.map(|it| self.resolve(*it)))
84 .unwrap_or_default(),
85 Value::List(list) => list.into_iter().map(|it| self.resolve(it)).collect(),
86 }
87 }
88}
89
90impl<'a> Iterator for Iter<'a> {
91 type Item = (&'a str, String);
92
93 fn next(&mut self) -> Option<Self::Item> {
94 while !self.input.is_empty() {
95 match parse(&mut self.input) {
96 Ok(Some((key, value))) => {
97 let resolved = self.resolve(value);
98 self.resolved.insert(key, resolved.clone());
99 return Some((key, resolved));
100 }
101 Ok(None) => {
102 }
104 Err(_) => {
105 break;
107 }
108 }
109 }
110
111 None
112 }
113}