use regex::Regex;
use std::collections::BTreeMap;
use std::fs;
use std::io::{self, BufRead};
use std::path::Path;
use super::common::*;
pub struct EnvFile {
vars: BTreeMap<String, String>,
}
impl EnvFile {
pub fn read<R: io::Read>(input: R) -> Result<EnvFile> {
let mut vars: BTreeMap<String, String> = BTreeMap::new();
let reader = io::BufReader::new(input);
for line_result in reader.lines() {
let line = line_result.map_err(Error::IoError)?;
lazy_static! {
static ref BLANK: Regex =
Regex::new(r#"^\s*(:?#.*)?$"#).unwrap();
static ref VAR: Regex =
Regex::new(r#"^([_A-Za-z][_A-Za-z0-9]*)=(.*)"#).unwrap();
}
if BLANK.is_match(&line) {
continue;
}
let caps = VAR.captures(&line).ok_or_else(|| Error::ParseEnv {
line: line.to_owned(),
})?;
vars.insert(
caps.get(1).unwrap().as_str().to_owned(),
caps.get(2).unwrap().as_str().to_owned(),
);
}
Ok(EnvFile { vars })
}
pub fn load(path: &Path) -> Result<EnvFile> {
let f = fs::File::open(path)
.map_err(|err| Error::read_file(path.to_owned(), err))?;
EnvFile::read(io::BufReader::new(f))
.map_err(|err| Error::read_file(path.to_owned(), err))
}
pub fn to_environment(&self) -> Result<BTreeMap<String, RawOr<String>>> {
let mut env = BTreeMap::new();
for (k, v) in &self.vars {
env.insert(k.to_owned(), escape(v)?);
}
Ok(env)
}
}
#[test]
fn parses_docker_compatible_env_files() {
let input = r#"
# This is a comment.
# This is a blank line:
# These are environment variables:
FOO=foo
BAR=2
# Docker does not currently do anything special with quotes!
WEIRD="quoted"
# TODO LOW: What if an .env file contains a shell variable interpolation?
"#;
let cursor = io::Cursor::new(input);
let env_file = EnvFile::read(cursor).unwrap();
let env = env_file.to_environment().unwrap();
assert_eq!(env.get("FOO").unwrap().value().unwrap(), "foo");
assert_eq!(env.get("BAR").unwrap().value().unwrap(), "2");
assert_eq!(env.get("WEIRD").unwrap().value().unwrap(), "\"quoted\"");
}