use crate::parse::*;
use std::collections::HashMap;
pub struct Dotenv {
buf: String,
}
fn normalize_input(buf: String) -> String {
let buf = if let Some(stripped) = buf.strip_prefix('\u{FEFF}') {
stripped.to_string()
} else {
buf
};
buf.replace("\r\n", "\n").replace('\r', "\n")
}
impl Dotenv {
pub(crate) fn new(buf: String) -> Self {
Self {
buf: normalize_input(buf),
}
}
pub fn iter(&self) -> Iter<'_> {
Iter::new(&self.buf)
}
pub fn load(self) {
self.set_vars(false)
}
pub fn load_override(self) {
self.set_vars(true)
}
fn set_vars(self, override_env: bool) {
for (key, value) in self.iter() {
if override_env || std::env::var(key).is_err() {
std::env::set_var(key, value);
}
}
}
}
pub struct Iter<'a> {
resolved: HashMap<&'a str, String>,
input: &'a str,
}
impl<'a> Iter<'a> {
pub(crate) fn new(input: &'a str) -> Self {
Self {
resolved: HashMap::new(),
input,
}
}
fn resolve_var(&self, name: &'a str) -> Option<String> {
std::env::var(name)
.ok()
.or_else(|| self.resolved.get(name).cloned())
.filter(|it| !it.is_empty())
}
fn resolve(&self, value: Value<'a>) -> String {
match value {
Value::Lit(text) => text.to_string(),
Value::Sub(name, fallback) => self
.resolve_var(name)
.or_else(|| fallback.map(|it| self.resolve(*it)))
.unwrap_or_default(),
Value::List(list) => list.into_iter().map(|it| self.resolve(it)).collect(),
}
}
}
impl<'a> Iterator for Iter<'a> {
type Item = (&'a str, String);
fn next(&mut self) -> Option<Self::Item> {
while !self.input.is_empty() {
match parse(&mut self.input) {
Ok(Some((key, value))) => {
let resolved = self.resolve(value);
self.resolved.insert(key, resolved.clone());
return Some((key, resolved));
}
Ok(None) => {
}
Err(_) => {
break;
}
}
}
None
}
}