Skip to main content

dotenv/
dotenv.rs

1use crate::parse::*;
2use std::collections::HashMap;
3
4/// Dotenv content
5pub struct Dotenv {
6    buf: String,
7}
8
9fn normalize_input(buf: String) -> String {
10    // Strip UTF-8 BOM (\u{FEFF}) if present — the parser does not expect it
11    let buf = if let Some(stripped) = buf.strip_prefix('\u{FEFF}') {
12        stripped.to_string()
13    } else {
14        buf
15    };
16
17    // Normalise line endings per spec: \r\n → \n, then standalone \r → \n
18    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    /// Return an iterator over the dotenv.
29    pub fn iter(&self) -> Iter<'_> {
30        Iter::new(&self.buf)
31    }
32
33    /// Load the dotenv into the current process's environment variables
34    ///
35    /// **NOTE**: The existing variables will be ignored.
36    pub fn load(self) {
37        self.set_vars(false)
38    }
39
40    /// Load the dotenv into the current process's environment variables
41    ///
42    /// **NOTE**: This will override the existing variables.
43    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
56/// Dotenv iterator
57pub 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    /// resolve **NON-EMPTY** variable
71    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                    // comment or blank line, continue
103                }
104                Err(_) => {
105                    // parse error
106                    break;
107                }
108            }
109        }
110
111        None
112    }
113}