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) {
43 self.set_vars(true)
44 }
45
46 fn set_vars(self, override_env: bool) {
47 for (key, value) in self.iter() {
48 if override_env || std::env::var(key).is_err() {
49 std::env::set_var(key, value);
50 }
51 }
52 }
53}
54
55pub struct Iter<'a> {
61 resolved: HashMap<&'a str, String>,
62 input: &'a str,
63}
64
65impl<'a> Iter<'a> {
66 pub(crate) fn new(input: &'a str) -> Self {
67 Self {
68 resolved: HashMap::new(),
69 input,
70 }
71 }
72
73 fn resolve_var(&self, name: &'a str) -> Option<String> {
78 std::env::var(name)
79 .ok()
80 .or_else(|| self.resolved.get(name).cloned())
81 .filter(|it| !it.is_empty())
82 }
83
84 fn resolve(&self, value: Value<'a>) -> String {
85 match value {
86 Value::Lit(text) => text.to_string(),
87 Value::Sub(name, fallback) => self
88 .resolve_var(name)
89 .or_else(|| fallback.map(|it| self.resolve(*it)))
90 .unwrap_or_default(),
91 Value::List(list) => list.into_iter().map(|it| self.resolve(it)).collect(),
92 }
93 }
94}
95
96impl<'a> Iterator for Iter<'a> {
97 type Item = (&'a str, String);
98
99 fn next(&mut self) -> Option<Self::Item> {
100 while !self.input.is_empty() {
101 match parse(&mut self.input) {
102 Ok(Some((key, value))) => {
103 let resolved = self.resolve(value);
104 self.resolved.insert(key, resolved.clone());
105 return Some((key, resolved));
106 }
107 Ok(None) => {
108 }
110 Err(_) => {
111 break;
113 }
114 }
115 }
116
117 None
118 }
119}