dotenv/
dotenv.rs

1use crate::parse::*;
2use std::collections::HashMap;
3
4pub struct Dotenv {
5    buf: String,
6}
7
8impl Dotenv {
9    pub(crate) fn new(buf: String) -> Self {
10        Self { buf }
11    }
12
13    pub fn iter(&self) -> Iter {
14        Iter::new(&self.buf)
15    }
16
17    pub fn load(self) {
18        self.set_vars(false)
19    }
20
21    pub fn load_override(self) {
22        self.set_vars(true)
23    }
24
25    fn set_vars(self, override_env: bool) {
26        for (key, value) in self.iter() {
27            if override_env || std::env::var(key).is_err() {
28                std::env::set_var(key, value);
29            }
30        }
31    }
32}
33
34pub struct Iter<'a> {
35    resolved: HashMap<&'a str, String>,
36    input: &'a str,
37}
38
39impl<'a> Iter<'a> {
40    pub(crate) fn new(input: &'a str) -> Self {
41        Self {
42            resolved: HashMap::new(),
43            input: strip_bom(input),
44        }
45    }
46
47    fn resolve_var(&self, name: &'a str) -> Option<String> {
48        std::env::var(name)
49            .ok()
50            .or_else(|| self.resolved.get(name).cloned())
51    }
52
53    fn resolve(&self, value: Value<'a>) -> Option<String> {
54        match value {
55            Value::Lit(text) => Some(text.to_string()),
56            Value::Var(name, default) => self
57                .resolve_var(name)
58                .or_else(|| default.and_then(|it| self.resolve(*it))),
59            Value::List(list) => Some(list.into_iter().flat_map(|it| self.resolve(it)).collect()),
60        }
61    }
62}
63
64impl<'a> Iterator for Iter<'a> {
65    type Item = (&'a str, String);
66
67    fn next(&mut self) -> Option<Self::Item> {
68        while let Ok((rest, maybe)) = parse(self.input) {
69            self.input = rest; // set next input
70
71            if let Some((key, value)) = maybe {
72                if let Some(value) = self.resolve(value) {
73                    self.resolved.insert(key, value.clone());
74                    return Some((key, value));
75                }
76            }
77
78            if rest.is_empty() {
79                break;
80            }
81        }
82
83        None
84    }
85}
86
87fn strip_bom(input: &str) -> &str {
88    // https://www.unicode.org/faq/utf_bom.html
89    input.strip_prefix('\u{FEFF}').unwrap_or(input)
90}