dotenv-0.15.0/Cargo.toml.orig000064400000000000000000000014771355336042200141650ustar0000000000000000[package] name = "dotenv" version = "0.15.0" authors = [ "Noemi Lapresta ", "Craig Hills ", "Mike Piccolo ", "Alice Maz ", "Sean Griffin ", "Adam Sharp ", "Arpad Borsos ", ] description = "A `dotenv` implementation for Rust" homepage = "https://github.com/dotenv-rs/dotenv" readme = "../README.md" keywords = ["environment", "env", "dotenv", "settings", "config"] license = "MIT" repository = "https://github.com/dotenv-rs/dotenv" edition = "2018" [[bin]] name = "dotenv" required-features = ["cli"] [dependencies] clap = { version = "2", optional = true } [dev-dependencies] tempfile = "3.0.0" [features] cli = ["clap"] dotenv-0.15.0/Cargo.toml0000644000000024320000000000000104540ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "dotenv" version = "0.15.0" authors = ["Noemi Lapresta ", "Craig Hills ", "Mike Piccolo ", "Alice Maz ", "Sean Griffin ", "Adam Sharp ", "Arpad Borsos "] description = "A `dotenv` implementation for Rust" homepage = "https://github.com/dotenv-rs/dotenv" readme = "../README.md" keywords = ["environment", "env", "dotenv", "settings", "config"] license = "MIT" repository = "https://github.com/dotenv-rs/dotenv" [[bin]] name = "dotenv" required-features = ["cli"] [dependencies.clap] version = "2" optional = true [dev-dependencies.tempfile] version = "3.0.0" [features] cli = ["clap"] dotenv-0.15.0/examples/simple.rs000064400000000000000000000002431355336042200147410ustar0000000000000000use dotenv::dotenv; use std::env; fn main() { dotenv().ok(); for (key, value) in env::vars() { println!("{}: {}", key, value); } } dotenv-0.15.0/src/bin/dotenv.rs000064400000000000000000000036611355336042200144770ustar0000000000000000extern crate clap; extern crate dotenv; use clap::{App, AppSettings, Arg}; use std::os::unix::process::CommandExt; use std::process::{Command, exit}; macro_rules! die { ($fmt:expr) => ({ eprintln!($fmt); exit(1); }); ($fmt:expr, $($arg:tt)*) => ({ eprintln!($fmt, $($arg)*); exit(1); }); } fn make_command(name: &str, args: Vec<&str>) -> Command { let mut command = Command::new(name); for arg in args { command.arg(arg); } return command; } fn main() { let matches = App::new("dotenv") .about("Run a command using the environment in a .env file") .usage("dotenv [ARGS]...") .setting(AppSettings::AllowExternalSubcommands) .setting(AppSettings::ArgRequiredElseHelp) .setting(AppSettings::UnifiedHelpMessage) .arg(Arg::with_name("FILE") .short("f") .long("file") .takes_value(true) .help("Use a specific .env file (defaults to .env)")) .get_matches(); match matches.value_of("FILE") { None => dotenv::dotenv(), Some(file) => dotenv::from_filename(file), }.unwrap_or_else(|e| die!("error: failed to load environment: {}", e)); let mut command = match matches.subcommand() { (name, Some(matches)) => { let args = matches.values_of("") .map(|v| v.collect()) .unwrap_or(Vec::new()); make_command(name, args) }, _ => die!("error: missing required argument "), }; if cfg!(target_os = "windows") { match command.spawn().and_then(|mut child| child.wait()) { Ok(status) => exit(status.code().unwrap_or(1)), Err(error) => die!("fatal: {}", error), }; } else { let error = command.exec(); die!("fatal: {}", error); }; } dotenv-0.15.0/src/errors.rs000064400000000000000000000062641355336042200137460ustar0000000000000000use std::io; use std::fmt; use std::error; pub type Result = std::result::Result; #[derive(Debug)] pub enum Error { LineParse(String, usize), Io(io::Error), EnvVar(std::env::VarError), #[doc(hidden)] __Nonexhaustive } impl Error { pub fn not_found(&self) -> bool { if let Error::Io(ref io_error) = *self { return io_error.kind() == io::ErrorKind::NotFound; } false } } impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { Error::Io(err) => Some(err), Error::EnvVar(err) => Some(err), _ => None, } } } impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match self { Error::Io(err) => write!(fmt, "{}", err), Error::EnvVar(err) => write!(fmt, "{}", err), Error::LineParse(line, error_index) => write!(fmt, "Error parsing line: '{}', error at line index: {}", line, error_index), _ => unreachable!(), } } } #[cfg(test)] mod test { use std::error::Error as StdError; use super::*; #[test] fn test_io_error_source() { let err = Error::Io(std::io::ErrorKind::PermissionDenied.into()); let io_err = err.source().unwrap().downcast_ref::().unwrap(); assert_eq!(std::io::ErrorKind::PermissionDenied, io_err.kind()); } #[test] fn test_envvar_error_source() { let err = Error::EnvVar(std::env::VarError::NotPresent); let var_err = err.source().unwrap().downcast_ref::().unwrap(); assert_eq!(&std::env::VarError::NotPresent, var_err); } #[test] fn test_lineparse_error_source() { let err = Error::LineParse("test line".to_string(), 2); assert!(err.source().is_none()); } #[test] fn test_error_not_found_true() { let err = Error::Io(std::io::ErrorKind::NotFound.into()); assert!(err.not_found()); } #[test] fn test_error_not_found_false() { let err = Error::Io(std::io::ErrorKind::PermissionDenied.into()); assert!(!err.not_found()); } #[test] fn test_io_error_display() { let err = Error::Io(std::io::ErrorKind::PermissionDenied.into()); let io_err: std::io::Error = std::io::ErrorKind::PermissionDenied.into(); let err_desc = format!("{}", err); let io_err_desc = format!("{}", io_err); assert_eq!(io_err_desc, err_desc); } #[test] fn test_envvar_error_display() { let err = Error::EnvVar(std::env::VarError::NotPresent); let var_err = std::env::VarError::NotPresent; let err_desc = format!("{}", err); let var_err_desc = format!("{}", var_err); assert_eq!(var_err_desc, err_desc); } #[test] fn test_lineparse_error_display() { let err = Error::LineParse("test line".to_string(), 2); let err_desc = format!("{}", err); assert_eq!("Error parsing line: 'test line', error at line index: 2", err_desc); } }dotenv-0.15.0/src/find.rs000064400000000000000000000026031355336042200133430ustar0000000000000000use std::fs::File; use std::path::{Path, PathBuf}; use std::{env, fs, io}; use crate::errors::*; use crate::iter::Iter; pub struct Finder<'a> { filename: &'a Path, } impl<'a> Finder<'a> { pub fn new() -> Self { Finder { filename: Path::new(".env"), } } pub fn filename(mut self, filename: &'a Path) -> Self { self.filename = filename; self } pub fn find(self) -> Result<(PathBuf, Iter)> { let path = find(&env::current_dir().map_err(Error::Io)?, self.filename)?; let file = File::open(&path).map_err(Error::Io)?; let iter = Iter::new(file); Ok((path, iter)) } } /// Searches for `filename` in `directory` and parent directories until found or root is reached. pub fn find(directory: &Path, filename: &Path) -> Result { let candidate = directory.join(filename); match fs::metadata(&candidate) { Ok(metadata) => if metadata.is_file() { return Ok(candidate); }, Err(error) => { if error.kind() != io::ErrorKind::NotFound { return Err(Error::Io(error)); } } } if let Some(parent) = directory.parent() { find(parent, filename) } else { Err(Error::Io(io::Error::new(io::ErrorKind::NotFound, "path not found"))) } } dotenv-0.15.0/src/iter.rs000064400000000000000000000024731355336042200133730ustar0000000000000000use std::collections::HashMap; use std::env; use std::io::{BufReader, Lines}; use std::io::prelude::*; use crate::errors::*; use crate::parse; pub struct Iter { lines: Lines>, substitution_data: HashMap>, } impl Iter { pub fn new(reader: R) -> Iter { Iter { lines: BufReader::new(reader).lines(), substitution_data: HashMap::new(), } } pub fn load(self) -> Result<()> { for item in self { let (key, value) = item?; if env::var(&key).is_err() { env::set_var(&key, value); } } Ok(()) } } impl Iterator for Iter { type Item = Result<(String, String)>; fn next(&mut self) -> Option { loop { let line = match self.lines.next() { Some(Ok(line)) => line, Some(Err(err)) => return Some(Err(Error::Io(err))), None => return None, }; match parse::parse_line(&line, &mut self.substitution_data) { Ok(Some(result)) => return Some(Ok(result)), Ok(None) => {} Err(err) => return Some(Err(err)), } } } } dotenv-0.15.0/src/lib.rs000064400000000000000000000124431355336042200131740ustar0000000000000000//! This crate provides a configuration loader in the style of the [ruby dotenv //! gem](https://github.com/bkeepers/dotenv). This library is meant to be used //! on development or testing environments in which setting environment //! variables is not practical. It loads environment variables from a .env //! file, if available, and mashes those with the actual environment variables //! provided by the operating system. mod parse; mod errors; mod iter; mod find; use std::env::{self, Vars}; use std::ffi::OsStr; use std::fs::File; use std::path::{Path, PathBuf}; use std::sync::{Once, ONCE_INIT}; pub use crate::errors::*; use crate::iter::Iter; use crate::find::Finder; static START: Once = ONCE_INIT; /// After loading the dotenv file, fetches the environment variable key from the current process. /// /// The returned result is Ok(s) if the environment variable is present and is valid unicode. If the /// environment variable is not present, or it is not valid unicode, then Err will be returned. /// /// Examples: /// /// ```no_run /// /// use dotenv; /// /// let key = "FOO"; /// let value= dotenv::var(key).unwrap(); /// ``` pub fn var>(key: K) -> Result { START.call_once(|| { dotenv().ok(); }); env::var(key).map_err(Error::EnvVar) } /// After loading the dotenv file, returns an iterator of (variable, value) pairs of strings, /// for all the environment variables of the current process. /// /// The returned iterator contains a snapshot of the process's environment variables at the /// time of this invocation, modifications to environment variables afterwards will not be /// reflected in the returned iterator. /// /// Examples: /// /// ```no_run /// /// use dotenv; /// use std::io; /// /// let result: Vec<(String, String)> = dotenv::vars().collect(); /// ``` pub fn vars() -> Vars { START.call_once(|| { dotenv().ok(); }); env::vars() } /// Loads the file at the specified absolute path. /// /// Examples /// /// ``` /// use dotenv; /// use std::env; /// use std::path::{Path}; /// /// let my_path = env::home_dir().and_then(|a| Some(a.join("/.env"))).unwrap(); /// dotenv::from_path(my_path.as_path()); /// ``` pub fn from_path>(path: P) -> Result<()> { let iter = Iter::new(File::open(path).map_err(Error::Io)?); iter.load() } /// Like `from_path`, but returns an iterator over variables instead of loading into environment. /// /// Examples /// /// ```no_run /// use dotenv; /// use std::env; /// use std::path::{Path}; /// /// let my_path = env::home_dir().and_then(|a| Some(a.join("/.env"))).unwrap(); /// let iter = dotenv::from_path_iter(my_path.as_path()).unwrap(); /// /// for item in iter { /// let (key, val) = item.unwrap(); /// println!("{}={}", key, val); /// } /// ``` #[deprecated(since = "0.14.1", note = "please use `from_path` in conjunction with `var` instead")] pub fn from_path_iter>(path: P) -> Result> { Ok(Iter::new(File::open(path).map_err(Error::Io)?)) } /// Loads the specified file from the environment's current directory or its parents in sequence. /// /// # Examples /// ``` /// use dotenv; /// dotenv::from_filename("custom.env").ok(); /// ``` /// /// It is also possible to do the following, but it is equivalent to using `dotenv::dotenv()`, /// which is preferred. /// /// ``` /// use dotenv; /// dotenv::from_filename(".env").ok(); /// ``` pub fn from_filename>(filename: P) -> Result { let (path, iter) = Finder::new().filename(filename.as_ref()).find()?; iter.load()?; Ok(path) } /// Like `from_filename`, but returns an iterator over variables instead of loading into environment. /// /// # Examples /// ``` /// use dotenv; /// dotenv::from_filename("custom.env").ok(); /// ``` /// /// It is also possible to do the following, but it is equivalent to using `dotenv::dotenv()`, /// which is preferred. /// /// ```no_run /// use dotenv; /// let iter = dotenv::from_filename_iter(".env").unwrap(); /// /// for item in iter { /// let (key, val) = item.unwrap(); /// println!("{}={}", key, val); /// } /// ``` #[deprecated(since = "0.14.1", note = "please use `from_path` in conjunction with `var` instead")] pub fn from_filename_iter>(filename: P) -> Result> { let (_, iter) = Finder::new().filename(filename.as_ref()).find()?; Ok(iter) } /// This is usually what you want. /// It loads the .env file located in the environment's current directory or its parents in sequence. /// /// # Examples /// ``` /// use dotenv; /// dotenv::dotenv().ok(); /// ``` pub fn dotenv() -> Result { let (path, iter) = Finder::new().find()?; iter.load()?; Ok(path) } /// Like `dotenv`, but returns an iterator over variables instead of loading into environment. /// /// # Examples /// ```no_run /// use dotenv; /// /// for item in dotenv::dotenv_iter().unwrap() { /// let (key, val) = item.unwrap(); /// println!("{}={}", key, val); /// } /// ``` #[deprecated(since = "0.14.1", note = "please use `from_path` in conjunction with `var` instead")] pub fn dotenv_iter() -> Result> { let (_, iter) = Finder::new().find()?; Ok(iter) } dotenv-0.15.0/src/parse.rs000064400000000000000000000444571355336042200135520ustar0000000000000000use std::collections::HashMap; use crate::errors::*; // for readability's sake pub type ParsedLine = Result>; pub fn parse_line(line: &str, substitution_data: &mut HashMap>) -> ParsedLine { let mut parser = LineParser::new(line, substitution_data); parser.parse_line() } struct LineParser<'a> { original_line: &'a str, substitution_data: &'a mut HashMap>, line: &'a str, pos: usize, } impl<'a> LineParser<'a> { fn new( line: &'a str, substitution_data: &'a mut HashMap>, ) -> LineParser<'a> { LineParser { original_line: line, substitution_data, line: line.trim_end(), // we don’t want trailing whitespace pos: 0, } } fn err(&self) -> Error { return Error::LineParse(self.original_line.into(), self.pos); } fn parse_line(&mut self) -> ParsedLine { self.skip_whitespace(); // if its an empty line or a comment, skip it if self.line.is_empty() || self.line.starts_with('#') { return Ok(None); } let mut key = self.parse_key()?; self.skip_whitespace(); // export can be either an optional prefix or a key itself if key == "export" { // here we check for an optional `=`, below we throw directly when it’s not found. if self.expect_equal().is_err() { key = self.parse_key()?; self.skip_whitespace(); self.expect_equal()?; } } else { self.expect_equal()?; } self.skip_whitespace(); if self.line.is_empty() || self.line.starts_with('#') { self.substitution_data.insert(key.clone(), None); return Ok(Some((key, String::new()))); } let parsed_value = parse_value(self.line, &mut self.substitution_data)?; self.substitution_data .insert(key.clone(), Some(parsed_value.clone())); return Ok(Some((key, parsed_value))); } fn parse_key(&mut self) -> Result { if !self .line .starts_with(|c: char| c.is_ascii_alphabetic() || c == '_') { return Err(self.err()); } let index = match self .line .find(|c: char| !(c.is_ascii_alphanumeric() || c == '_' || c == '.')) { Some(index) => index, None => self.line.len(), }; self.pos += index; let key = String::from(&self.line[..index]); self.line = &self.line[index..]; Ok(key) } fn expect_equal(&mut self) -> Result<()> { if !self.line.starts_with("=") { return Err(self.err()); } self.line = &self.line[1..]; self.pos += 1; Ok(()) } fn skip_whitespace(&mut self) { if let Some(index) = self.line.find(|c: char| !c.is_whitespace()) { self.pos += index; self.line = &self.line[index..]; } else { self.pos += self.line.len(); self.line = ""; } } } #[derive(Eq, PartialEq)] enum SubstitutionMode { None, Block, EscapedBlock, } fn parse_value(input: &str, substitution_data: &mut HashMap>) -> Result { let mut strong_quote = false; // ' let mut weak_quote = false; // " let mut escaped = false; let mut expecting_end = false; //FIXME can this be done without yet another allocation per line? let mut output = String::new(); let mut substitution_mode = SubstitutionMode::None; let mut substitution_name = String::new(); for (index, c) in input.chars().enumerate() { //the regex _should_ already trim whitespace off the end //expecting_end is meant to permit: k=v #comment //without affecting: k=v#comment //and throwing on: k=v w if expecting_end { if c == ' ' || c == '\t' { continue; } else if c == '#' { break; } else { return Err(Error::LineParse(input.to_owned(), index)); } } else if escaped { //TODO I tried handling literal \r but various issues //imo not worth worrying about until there's a use case //(actually handling backslash 0x10 would be a whole other matter) //then there's \v \f bell hex... etc match c { '\\' | '\'' | '"' | '$' | ' ' => output.push(c), 'n' => output.push('\n'), // handle \n case _ => { return Err(Error::LineParse(input.to_owned(), index)); } } escaped = false; } else if strong_quote { if c == '\'' { strong_quote = false; } else { output.push(c); } } else if substitution_mode != SubstitutionMode::None { if c.is_alphanumeric() { substitution_name.push(c); } else { match substitution_mode { SubstitutionMode::None => unreachable!(), SubstitutionMode::Block => { if c == '{' && substitution_name.is_empty() { substitution_mode = SubstitutionMode::EscapedBlock; } else { apply_substitution(substitution_data, &substitution_name.drain(..).collect::(), &mut output); if c == '$' { substitution_mode = if !strong_quote && !escaped { SubstitutionMode::Block } else { SubstitutionMode::None } } else { substitution_mode = SubstitutionMode::None; output.push(c); } } } SubstitutionMode::EscapedBlock => { if c == '}' { substitution_mode = SubstitutionMode::None; apply_substitution(substitution_data, &substitution_name.drain(..).collect::(), &mut output); } else { substitution_name.push(c); } } } } } else if c == '$' { substitution_mode = if !strong_quote && !escaped { SubstitutionMode::Block } else { SubstitutionMode::None } } else if weak_quote { if c == '"' { weak_quote = false; } else if c == '\\' { escaped = true; } else { output.push(c); } } else if c == '\'' { strong_quote = true; } else if c == '"' { weak_quote = true; } else if c == '\\' { escaped = true; } else if c == ' ' || c == '\t' { expecting_end = true; } else { output.push(c); } } //XXX also fail if escaped? or... if substitution_mode == SubstitutionMode::EscapedBlock || strong_quote || weak_quote { let value_length = input.len(); Err(Error::LineParse(input.to_owned(), if value_length == 0 { 0 } else { value_length - 1 })) } else { apply_substitution(substitution_data, &substitution_name.drain(..).collect::(), &mut output); Ok(output) } } fn apply_substitution(substitution_data: &mut HashMap>, substitution_name: &str, output: &mut String) { if let Ok(environment_value) = std::env::var(substitution_name) { output.push_str(&environment_value); } else { let stored_value = substitution_data.get(substitution_name).unwrap_or(&None).to_owned(); output.push_str(&stored_value.unwrap_or_else(String::new)); }; } #[cfg(test)] mod test { use crate::iter::Iter; use super::*; #[test] fn test_parse_line_env() { // Note 5 spaces after 'KEY8=' below let actual_iter = Iter::new(r#" KEY=1 KEY2="2" KEY3='3' KEY4='fo ur' KEY5="fi ve" KEY6=s\ ix KEY7= KEY8= KEY9= # foo KEY10 ="whitespace before =" KEY11= "whitespace after =" export="export as key" export SHELL_LOVER=1 "#.as_bytes()); let expected_iter = vec![ ("KEY", "1"), ("KEY2", "2"), ("KEY3", "3"), ("KEY4", "fo ur"), ("KEY5", "fi ve"), ("KEY6", "s ix"), ("KEY7", ""), ("KEY8", ""), ("KEY9", ""), ("KEY10", "whitespace before ="), ("KEY11", "whitespace after ="), ("export", "export as key"), ("SHELL_LOVER", "1"), ].into_iter() .map(|(key, value)| (key.to_string(), value.to_string())); let mut count = 0; for (expected, actual) in expected_iter.zip(actual_iter) { assert!(actual.is_ok()); assert_eq!(expected, actual.ok().unwrap()); count += 1; } assert_eq!(count, 13); } #[test] fn test_parse_line_comment() { let result: Result> = Iter::new(r#" # foo=bar # "#.as_bytes()).collect(); assert!(result.unwrap().is_empty()); } #[test] fn test_parse_line_invalid() { // Note 4 spaces after 'invalid' below let actual_iter = Iter::new(r#" invalid very bacon = yes indeed =value"#.as_bytes()); let mut count = 0; for actual in actual_iter { assert!(actual.is_err()); count += 1; } assert_eq!(count, 3); } #[test] fn test_parse_value_escapes() { let actual_iter = Iter::new(r#" KEY=my\ cool\ value KEY2=\$sweet KEY3="awesome stuff \"mang\"" KEY4='sweet $\fgs'\''fds' KEY5="'\"yay\\"\ "stuff" KEY6="lol" #well you see when I say lol wh KEY7="line 1\nline 2" "#.as_bytes()); let expected_iter = vec![ ("KEY", r#"my cool value"#), ("KEY2", r#"$sweet"#), ("KEY3", r#"awesome stuff "mang""#), ("KEY4", r#"sweet $\fgs'fds"#), ("KEY5", r#"'"yay\ stuff"#), ("KEY6", "lol"), ("KEY7", "line 1\nline 2"), ].into_iter() .map(|(key, value)| (key.to_string(), value.to_string())); for (expected, actual) in expected_iter.zip(actual_iter) { assert!(actual.is_ok()); assert_eq!(expected, actual.unwrap()); } } #[test] fn test_parse_value_escapes_invalid() { let actual_iter = Iter::new(r#" KEY=my uncool value KEY2="why KEY3='please stop'' KEY4=h\8u "#.as_bytes()); for actual in actual_iter { assert!(actual.is_err()); } } } #[cfg(test)] mod variable_substitution_tests { use crate::iter::Iter; fn assert_parsed_string(input_string: &str, expected_parse_result: Vec<(&str, &str)>) { let actual_iter = Iter::new(input_string.as_bytes()); let expected_count = &expected_parse_result.len(); let expected_iter = expected_parse_result.into_iter() .map(|(key, value)| (key.to_string(), value.to_string())); let mut count = 0; for (expected, actual) in expected_iter.zip(actual_iter) { assert!(actual.is_ok()); assert_eq!(expected, actual.ok().unwrap()); count += 1; } assert_eq!(count, *expected_count); } #[test] fn variable_in_parenthesis_surrounded_by_quotes() { assert_parsed_string( r#" KEY=test KEY1="${KEY}" "#, vec![ ("KEY", "test"), ("KEY1", "test"), ], ); } #[test] fn substitute_undefined_variables_to_empty_string() { assert_parsed_string( r#"KEY=">$KEY1<>${KEY2}<""#, vec![ ("KEY", "><><"), ], ); } #[test] fn do_not_substitute_variables_with_dollar_escaped() { assert_parsed_string( "KEY=>\\$KEY1<>\\${KEY2}<", vec![ ("KEY", ">$KEY1<>${KEY2}<"), ], ); } #[test] fn do_not_substitute_variables_in_weak_quotes_with_dollar_escaped() { assert_parsed_string( r#"KEY=">\$KEY1<>\${KEY2}<""#, vec![ ("KEY", ">$KEY1<>${KEY2}<"), ], ); } #[test] fn do_not_substitute_variables_in_strong_quotes() { assert_parsed_string( "KEY='>${KEY1}<>$KEY2<'", vec![ ("KEY", ">${KEY1}<>$KEY2<"), ], ); } #[test] fn same_variable_reused() { assert_parsed_string( r#" KEY=VALUE KEY1=$KEY$KEY "#, vec![ ("KEY", "VALUE"), ("KEY1", "VALUEVALUE"), ], ); } #[test] fn with_dot() { assert_parsed_string( r#" KEY.Value=VALUE "#, vec![ ("KEY.Value", "VALUE"), ], ); } #[test] fn recursive_substitution() { assert_parsed_string( r#" KEY=${KEY1}+KEY_VALUE KEY1=${KEY}+KEY1_VALUE "#, vec![ ("KEY", "+KEY_VALUE"), ("KEY1", "+KEY_VALUE+KEY1_VALUE"), ], ); } #[test] fn variable_without_parenthesis_is_substituted_before_separators() { assert_parsed_string( r#" KEY1=test_user KEY1_1=test_user_with_separator KEY=">$KEY1_1<>$KEY1}<>$KEY1{<" "#, vec![ ("KEY1", "test_user"), ("KEY1_1", "test_user_with_separator"), ("KEY", ">test_user_1<>test_user}<>test_user{<"), ], ); } #[test] fn substitute_variable_from_env_variable() { std::env::set_var("KEY11", "test_user_env"); assert_parsed_string( r#"KEY=">${KEY11}<""#, vec![ ("KEY", ">test_user_env<"), ], ); } #[test] fn substitute_variable_env_variable_overrides_dotenv_in_substitution() { std::env::set_var("KEY11", "test_user_env"); assert_parsed_string( r#" KEY11=test_user KEY=">${KEY11}<" "#, vec![ ("KEY11", "test_user"), ("KEY", ">test_user_env<"), ], ); } #[test] fn consequent_substitutions() { assert_parsed_string( r#" KEY1=test_user KEY2=$KEY1_2 KEY=>${KEY1}<>${KEY2}< "#, vec![ ("KEY1", "test_user"), ("KEY2", "test_user_2"), ("KEY", ">test_user<>test_user_2<"), ], ); } #[test] fn consequent_substitutions_with_one_missing() { assert_parsed_string( r#" KEY2=$KEY1_2 KEY=>${KEY1}<>${KEY2}< "#, vec![ ("KEY2", "_2"), ("KEY", "><>_2<"), ], ); } } #[cfg(test)] mod error_tests { use crate::errors::Error::LineParse; use crate::iter::Iter; #[test] fn should_not_parse_unfinished_substitutions() { let wrong_value = ">${KEY{<"; let parsed_values: Vec<_> = Iter::new(format!(r#" KEY=VALUE KEY1={} "#, wrong_value).as_bytes()).collect(); assert_eq!(parsed_values.len(), 2); if let Ok(first_line) = &parsed_values[0] { assert_eq!(first_line, &(String::from("KEY"), String::from("VALUE"))) } else { assert!(false, "Expected the first value to be parsed") } if let Err(LineParse(second_value, index)) = &parsed_values[1] { assert_eq!(second_value, wrong_value); assert_eq!(*index, wrong_value.len() - 1) } else { assert!(false, "Expected the second value not to be parsed") } } #[test] fn should_not_allow_dot_as_first_character_of_key() { let wrong_key_value = ".Key=VALUE"; let parsed_values: Vec<_> = Iter::new(wrong_key_value.as_bytes()).collect(); assert_eq!(parsed_values.len(), 1); if let Err(LineParse(second_value, index)) = &parsed_values[0] { assert_eq!(second_value, wrong_key_value); assert_eq!(*index, 0) } else { assert!(false, "Expected the second value not to be parsed") } } #[test] fn should_not_parse_illegal_format() { let wrong_format = r"<><><>"; let parsed_values: Vec<_> = Iter::new(wrong_format.as_bytes()).collect(); assert_eq!(parsed_values.len(), 1); if let Err(LineParse(wrong_value, index)) = &parsed_values[0] { assert_eq!(wrong_value, wrong_format); assert_eq!(*index, 0) } else { assert!(false, "Expected the second value not to be parsed") } } #[test] fn should_not_parse_illegal_escape() { let wrong_escape = r">\f<"; let parsed_values: Vec<_> = Iter::new(format!("VALUE={}", wrong_escape).as_bytes()).collect(); assert_eq!(parsed_values.len(), 1); if let Err(LineParse(wrong_value, index)) = &parsed_values[0] { assert_eq!(wrong_value, wrong_escape); assert_eq!(*index, wrong_escape.find("\\").unwrap() + 1) } else { assert!(false, "Expected the second value not to be parsed") } } } dotenv-0.15.0/tests/common/mod.rs000064400000000000000000000010431355336042200150420ustar0000000000000000use std::{env, io}; use std::fs::File; use std::io::prelude::*; use tempfile::{tempdir, TempDir}; pub fn tempdir_with_dotenv(dotenv_text: &str) -> io::Result { let dir = tempdir()?; env::set_current_dir(dir.path())?; let dotenv_path = dir.path().join(".env"); let mut dotenv_file = File::create(dotenv_path)?; dotenv_file.write_all(dotenv_text.as_bytes())?; dotenv_file.sync_all()?; Ok(dir) } pub fn make_test_dotenv() -> io::Result { tempdir_with_dotenv("TESTKEY=test_val") } dotenv-0.15.0/tests/test-child-dir.rs000064400000000000000000000006461355336042200156170ustar0000000000000000mod common; use std::{env, fs}; use dotenv::*; use crate::common::*; #[test] fn test_child_dir() { let dir = make_test_dotenv().unwrap(); fs::create_dir("child").unwrap(); env::set_current_dir("child").unwrap(); dotenv().ok(); assert_eq!(env::var("TESTKEY").unwrap(), "test_val"); env::set_current_dir(dir.path().parent().unwrap()).unwrap(); dir.close().unwrap(); } dotenv-0.15.0/tests/test-default-location.rs000064400000000000000000000005171355336042200172070ustar0000000000000000mod common; use std::env; use dotenv::*; use crate::common::*; #[test] fn test_default_location() { let dir = make_test_dotenv().unwrap(); dotenv().ok(); assert_eq!(env::var("TESTKEY").unwrap(), "test_val"); env::set_current_dir(dir.path().parent().unwrap()).unwrap(); dir.close().unwrap(); } dotenv-0.15.0/tests/test-dotenv-iter.rs000064400000000000000000000006751355336042200162220ustar0000000000000000mod common; use std::env; use dotenv::*; use crate::common::*; #[test] #[allow(deprecated)] fn test_dotenv_iter() { let dir = make_test_dotenv().unwrap(); let iter = dotenv_iter().unwrap(); assert!(env::var("TESTKEY").is_err()); iter.load().ok(); assert_eq!(env::var("TESTKEY").unwrap(), "test_val"); env::set_current_dir(dir.path().parent().unwrap()).unwrap(); dir.close().unwrap(); } dotenv-0.15.0/tests/test-from-filename-iter.rs000064400000000000000000000007211355336042200174340ustar0000000000000000mod common; use std::env; use dotenv::*; use crate::common::*; #[test] #[allow(deprecated)] fn test_from_filename_iter() { let dir = make_test_dotenv().unwrap(); let iter = from_filename_iter(".env").unwrap(); assert!(env::var("TESTKEY").is_err()); iter.load().ok(); assert_eq!(env::var("TESTKEY").unwrap(), "test_val"); env::set_current_dir(dir.path().parent().unwrap()).unwrap(); dir.close().unwrap(); } dotenv-0.15.0/tests/test-from-filename.rs000064400000000000000000000005331355336042200164740ustar0000000000000000mod common; use std::env; use dotenv::*; use crate::common::*; #[test] fn test_from_filename() { let dir = make_test_dotenv().unwrap(); from_filename(".env").ok(); assert_eq!(env::var("TESTKEY").unwrap(), "test_val"); env::set_current_dir(dir.path().parent().unwrap()).unwrap(); dir.close().unwrap(); } dotenv-0.15.0/tests/test-from-path-iter.rs000064400000000000000000000010231355336042200166040ustar0000000000000000mod common; use std::env; use dotenv::*; use crate::common::*; #[test] #[allow(deprecated)] fn test_from_path_iter() { let dir = make_test_dotenv().unwrap(); let mut path = env::current_dir().unwrap(); path.push(".env"); let iter = from_path_iter(&path).unwrap(); assert!(env::var("TESTKEY").is_err()); iter.load().ok(); assert_eq!(env::var("TESTKEY").unwrap(), "test_val"); env::set_current_dir(dir.path().parent().unwrap()).unwrap(); dir.close().unwrap(); } dotenv-0.15.0/tests/test-from-path.rs000064400000000000000000000006351355336042200156530ustar0000000000000000mod common; use std::env; use dotenv::*; use crate::common::*; #[test] fn test_from_path() { let dir = make_test_dotenv().unwrap(); let mut path = env::current_dir().unwrap(); path.push(".env"); from_path(&path).ok(); assert_eq!(env::var("TESTKEY").unwrap(), "test_val"); env::set_current_dir(dir.path().parent().unwrap()).unwrap(); dir.close().unwrap(); } dotenv-0.15.0/tests/test-var.rs000064400000000000000000000004531355336042200145440ustar0000000000000000mod common; use std::env; use dotenv::*; use crate::common::*; #[test] fn test_var() { let dir = make_test_dotenv().unwrap(); assert_eq!(var("TESTKEY").unwrap(), "test_val"); env::set_current_dir(dir.path().parent().unwrap()).unwrap(); dir.close().unwrap(); } dotenv-0.15.0/tests/test-variable-substitution.rs000064400000000000000000000026441355336042200203170ustar0000000000000000mod common; use std::env; use dotenv::*; use crate::common::*; #[test] fn test_variable_substitutions() { std::env::set_var("KEY", "value"); std::env::set_var("KEY1", "value1"); let substitutions_to_test = [ "$ZZZ", "$KEY", "$KEY1", "${KEY}1", "$KEY_U", "${KEY_U}", "\\$KEY" ]; let common_string = substitutions_to_test.join(">>"); let dir = tempdir_with_dotenv(&format!(r#" KEY1=new_value1 KEY_U=$KEY+valueU SUBSTITUTION_FOR_STRONG_QUOTES='{}' SUBSTITUTION_FOR_WEAK_QUOTES="{}" SUBSTITUTION_WITHOUT_QUOTES={} "#, common_string, common_string, common_string)).unwrap(); assert_eq!(var("KEY").unwrap(), "value"); assert_eq!(var("KEY1").unwrap(), "value1"); assert_eq!(var("KEY_U").unwrap(), "value+valueU"); assert_eq!(var("SUBSTITUTION_FOR_STRONG_QUOTES").unwrap(), common_string); assert_eq!(var("SUBSTITUTION_FOR_WEAK_QUOTES").unwrap(), [ "", "value", "value1", "value1", "value_U", "value+valueU", "$KEY" ].join(">>")); assert_eq!(var("SUBSTITUTION_WITHOUT_QUOTES").unwrap(), [ "", "value", "value1", "value1", "value_U", "value+valueU", "$KEY" ].join(">>")); env::set_current_dir(dir.path().parent().unwrap()).unwrap(); dir.close().unwrap(); }dotenv-0.15.0/tests/test-vars.rs000064400000000000000000000006071355336042200147300ustar0000000000000000mod common; use std::collections::HashMap; use std::env; use dotenv::*; use crate::common::*; #[test] fn test_vars() { let dir = make_test_dotenv().unwrap(); let vars: HashMap = vars().collect(); assert_eq!(vars["TESTKEY"], "test_val"); env::set_current_dir(dir.path().parent().unwrap()).unwrap(); dir.close().unwrap(); } dotenv-0.15.0/.cargo_vcs_info.json0000644000000001120000000000000124470ustar00{ "git": { "sha1": "fa467c4febe3e1b1f406fbe5e3550afedffe5d82" } } dotenv-0.15.0/Cargo.lock0000644000000224630000000000000104370ustar00# This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "ansi_term" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "atty" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "c2-chacha" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "clap" version = "2.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "dotenv" version = "0.15.0" dependencies = [ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "getrandom" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", "wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "libc" version = "0.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "ppv-lite86" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "rand" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rand_chacha" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "redox_syscall" version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "remove_dir_all" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "tempfile" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "unicode-width" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "vec_map" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "wasi" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" "checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" "checksum getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "473a1265acc8ff1e808cd0a1af8cee3c2ee5200916058a2ca113c29f2d903571" "checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8" "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" "checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" "checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"