Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement overriding API methods and correct inconsistent naming #47

Merged
merged 2 commits into from Dec 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 11 additions & 3 deletions dotenv/src/iter.rs
Expand Up @@ -21,7 +21,11 @@ impl<R: Read> Iter<R> {
}
}

/// Loads all variables found in the `reader` into the environment.
/// Loads all variables found in the `reader` into the environment,
/// preserving any existing environment variables of the same name.
///
/// If a variable is specified multiple times within the reader's data,
/// then the first occurrence is applied.
pub fn load(mut self) -> Result<()> {
self.remove_bom()?;

Expand All @@ -35,8 +39,12 @@ impl<R: Read> Iter<R> {
Ok(())
}

/// A version of [load] that overrides older variables in case of duplicates.
pub fn overload(mut self) -> Result<()> {
/// Loads all variables found in the `reader` into the environment,
/// overriding any existing environment variables of the same name.
///
/// If a variable is specified multiple times within the reader's data,
/// then the last occurrence is applied.
pub fn load_override(mut self) -> Result<()> {
self.remove_bom()?;

for item in self {
Expand Down
143 changes: 133 additions & 10 deletions dotenv/src/lib.rs
Expand Up @@ -60,6 +60,15 @@ pub fn vars() -> Vars {

/// Loads environment variables from the specified path.
///
/// If variables with the same names already exist in the environment, then their values will be
/// preserved.
///
/// Where multiple declarations for the same environment variable exist in your *.env*
/// file, the *first one* is applied.
///
/// If you wish to ensure all variables are loaded from your *.env* file, ignoring variables
/// already existing in the environment, then use [`from_path_override`] instead.
///
/// # Examples
///
/// ```no_run
Expand All @@ -73,7 +82,30 @@ pub fn from_path<P: AsRef<Path>>(path: P) -> Result<()> {
iter.load()
}

/// Returns an iterator over environment variables from the specified path.
/// Loads environment variables from the specified path,
/// overriding existing environment variables.
///
/// Where multiple declarations for the same environment variable exist in your *.env* file, the
/// *last one* is applied.
///
/// If you want the existing environment to take precedence,
/// or if you want to be able to override environment variables on the command line,
/// then use [`from_path`] instead.
///
/// # Examples
///
/// ```no_run
/// use dirs::home_dir;
///
/// let my_path = home_dir().map(|a| a.join("/absolute/path/.env")).unwrap();
/// dotenvy::from_path_override(my_path.as_path());
/// ```
pub fn from_path_override<P: AsRef<Path>>(path: P) -> Result<()> {
let iter = Iter::new(File::open(path).map_err(Error::Io)?);
iter.load_override()
}

/// Returns an iterator over environment variables from the specified path.
///
/// # Examples
///
Expand All @@ -93,12 +125,21 @@ pub fn from_path_iter<P: AsRef<Path>>(path: P) -> Result<Iter<File>> {

/// Loads environment variables from the specified file.
///
/// If variables with the same names already exist in the environment, then their values will be
/// preserved.
///
/// Where multiple declarations for the same environment variable exist in your *.env*
/// file, the *first one* is applied.
///
/// If you wish to ensure all variables are loaded from your *.env* file, ignoring variables
/// already existing in the environment, then use [`from_filename_override`] instead.
///
/// # Examples
/// ```no_run
/// dotenvy::from_filename("custom.env").unwrap();
/// ```
///
/// It is also possible to load from a typical *.env* file like so. However, using [dotenv] is preferred.
/// It is also possible to load from a typical *.env* file like so. However, using [`dotenv`] is preferred.
///
/// ```
/// dotenvy::from_filename(".env").unwrap();
Expand All @@ -109,6 +150,32 @@ pub fn from_filename<P: AsRef<Path>>(filename: P) -> Result<PathBuf> {
Ok(path)
}

/// Loads environment variables from the specified file,
/// overriding existing environment variables.
///
/// Where multiple declarations for the same environment variable exist in your *.env* file, the
/// *last one* is applied.
///
/// If you want the existing environment to take precedence,
/// or if you want to be able to override environment variables on the command line,
/// then use [`from_filename`] instead.
///
/// # Examples
/// ```no_run
/// dotenvy::from_filename_override("custom.env").unwrap();
/// ```
///
/// It is also possible to load from a typical *.env* file like so. However, using [`dotenv_override`] is preferred.
///
/// ```
/// dotenvy::from_filename_override(".env").unwrap();
/// ```
pub fn from_filename_override<P: AsRef<Path>>(filename: P) -> Result<PathBuf> {
let (path, iter) = Finder::new().filename(filename.as_ref()).find()?;
iter.load_override()?;
Ok(path)
}

/// Returns an iterator over environment variables from the specified file.
///
/// # Examples
Expand All @@ -125,11 +192,20 @@ pub fn from_filename_iter<P: AsRef<Path>>(filename: P) -> Result<Iter<File>> {
Ok(iter)
}

/// Loads environment variables from [io::Read](std::io::Read).
/// Loads environment variables from [`io::Read`](std::io::Read).
///
/// This is useful for loading environment variables from from IPC or the network.
///
/// For regular files, use [from_path] or [from_filename].
/// If variables with the same names already exist in the environment, then their values will be
/// preserved.
///
/// Where multiple declarations for the same environment variable exist in your `reader`,
/// the *first one* is applied.
///
/// If you wish to ensure all variables are loaded from your `reader`, ignoring variables
/// already existing in the environment, then use [`from_read_override`] instead.
///
/// For regular files, use [`from_path`] or [`from_filename`].
///
/// # Examples
///
Expand All @@ -147,7 +223,36 @@ pub fn from_read<R: io::Read>(reader: R) -> Result<()> {
Ok(())
}

/// Returns an iterator over environment variables from [io::Read](std::io::Read).
/// Loads environment variables from [`io::Read`](std::io::Read),
/// overriding existing environment variables.
///
/// This is useful for loading environment variables from from IPC or the network.
///
/// Where multiple declarations for the same environment variable exist in your `reader`, the
/// *last one* is applied.
///
/// If you want the existing environment to take precedence,
/// or if you want to be able to override environment variables on the command line,
/// then use [`from_read`] instead.
///
/// For regular files, use [`from_path_override`] or [`from_filename_override`].
///
/// # Examples
/// ```no_run
/// # #![cfg(unix)]
/// use std::io::Read;
/// use std::os::unix::net::UnixStream;
///
/// let mut stream = UnixStream::connect("/some/socket").unwrap();
/// dotenvy::from_read_override(stream).unwrap();
/// ```
pub fn from_read_override<R: io::Read>(reader: R) -> Result<()> {
let iter = Iter::new(reader);
iter.load_override()?;
Ok(())
}

/// Returns an iterator over environment variables from [`io::Read`](std::io::Read).
///
/// # Examples
///
Expand All @@ -166,8 +271,18 @@ pub fn from_read<R: io::Read>(reader: R) -> Result<()> {
pub fn from_read_iter<R: io::Read>(reader: R) -> Iter<R> {
Iter::new(reader)
}

/// Loads the *.env* file from the current directory or parents. This is typically what you want.
///
/// If variables with the same names already exist in the environment, then their values will be
/// preserved.
///
/// Where multiple declarations for the same environment variable exist in your *.env*
/// file, the *first one* is applied.
///
/// If you wish to ensure all variables are loaded from your *.env* file, ignoring variables
/// already existing in the environment, then use [`dotenv_override`] instead.
///
/// An error will be returned if the file is not found.
///
/// # Examples
Expand All @@ -181,16 +296,24 @@ pub fn dotenv() -> Result<PathBuf> {
Ok(path)
}

/// A version of [dotenv] that overrides existing env vars
/// Loads all variables found in the `reader` into the environment,
/// overriding any existing environment variables of the same name.
///
/// Where multiple declarations for the same environment variable exist in your *.env* file, the
/// *last one* is applied.
///
/// If you want the existing environment to take precedence,
/// or if you want to be able to override environment variables on the command line,
/// then use [`dotenv`] instead.
///
/// # Examples
/// ```
/// use dotenvy::overload;
/// overload().ok();
/// use dotenvy::dotenv_override;
/// dotenv_override().ok();
/// ```
pub fn overload() -> Result<PathBuf> {
pub fn dotenv_override() -> Result<PathBuf> {
let (path, iter) = Finder::new().find()?;
iter.overload()?;
iter.load_override()?;
Ok(path)
}

Expand Down
3 changes: 2 additions & 1 deletion dotenv/tests/common/mod.rs
Expand Up @@ -4,6 +4,7 @@ use std::{env, io};
use tempfile::{tempdir, TempDir};

pub fn tempdir_with_dotenv(dotenv_text: &str) -> io::Result<TempDir> {
env::set_var("EXISTING", "from_env");
let dir = tempdir()?;
env::set_current_dir(dir.path())?;
let dotenv_path = dir.path().join(".env");
Expand All @@ -15,5 +16,5 @@ pub fn tempdir_with_dotenv(dotenv_text: &str) -> io::Result<TempDir> {

#[allow(dead_code)]
pub fn make_test_dotenv() -> io::Result<TempDir> {
tempdir_with_dotenv("TESTKEY=test_val")
tempdir_with_dotenv("TESTKEY=test_val\nTESTKEY=test_val_overridden\nEXISTING=from_file")
}
21 changes: 21 additions & 0 deletions dotenv/tests/test-default-location-override.rs
@@ -0,0 +1,21 @@
mod common;

use std::{env, error::Error, result::Result};

use dotenvy::*;

use crate::common::*;

#[test]
fn test_default_location_override() -> Result<(), Box<dyn Error>> {
let dir = make_test_dotenv()?;

dotenv_override()?;

assert_eq!(env::var("TESTKEY")?, "test_val_overridden");
assert_eq!(env::var("EXISTING")?, "from_file");

env::set_current_dir(dir.path().parent().unwrap())?;
dir.close()?;
Ok(())
}
2 changes: 2 additions & 0 deletions dotenv/tests/test-default-location.rs
Expand Up @@ -11,7 +11,9 @@ fn test_default_location() -> Result<(), Box<dyn Error>> {
let dir = make_test_dotenv()?;

dotenv()?;

assert_eq!(env::var("TESTKEY")?, "test_val");
assert_eq!(env::var("EXISTING")?, "from_env");

env::set_current_dir(dir.path().parent().unwrap())?;
dir.close()?;
Expand Down
20 changes: 20 additions & 0 deletions dotenv/tests/test-from-filename-override.rs
@@ -0,0 +1,20 @@
mod common;

use dotenvy::*;
use std::{env, error::Error, result::Result};

use crate::common::*;

#[test]
fn test_from_filename_override() -> Result<(), Box<dyn Error>> {
let dir = make_test_dotenv()?;

from_filename_override(".env")?;

assert_eq!(env::var("TESTKEY")?, "test_val_overridden");
assert_eq!(env::var("EXISTING")?, "from_file");

env::set_current_dir(dir.path().parent().unwrap())?;
dir.close()?;
Ok(())
}
1 change: 1 addition & 0 deletions dotenv/tests/test-from-filename.rs
Expand Up @@ -12,6 +12,7 @@ fn test_from_filename() -> Result<(), Box<dyn Error>> {
from_filename(".env")?;

assert_eq!(env::var("TESTKEY")?, "test_val");
assert_eq!(env::var("EXISTING")?, "from_env");

env::set_current_dir(dir.path().parent().unwrap())?;
dir.close()?;
Expand Down
22 changes: 22 additions & 0 deletions dotenv/tests/test-from-path-override.rs
@@ -0,0 +1,22 @@
mod common;

use crate::common::*;
use dotenvy::*;
use std::{env, error::Error, result::Result};

#[test]
fn test_from_path_override() -> Result<(), Box<dyn Error>> {
let dir = make_test_dotenv()?;

let mut path = env::current_dir()?;
path.push(".env");

from_path_override(&path)?;

assert_eq!(env::var("TESTKEY")?, "test_val_overridden");
assert_eq!(env::var("EXISTING")?, "from_file");

env::set_current_dir(dir.path().parent().unwrap())?;
dir.close()?;
Ok(())
}
1 change: 1 addition & 0 deletions dotenv/tests/test-from-path.rs
Expand Up @@ -14,6 +14,7 @@ fn test_from_path() -> Result<(), Box<dyn Error>> {
from_path(&path)?;

assert_eq!(env::var("TESTKEY")?, "test_val");
assert_eq!(env::var("EXISTING")?, "from_env");

env::set_current_dir(dir.path().parent().unwrap())?;
dir.close()?;
Expand Down
20 changes: 20 additions & 0 deletions dotenv/tests/test-from-read-override.rs
@@ -0,0 +1,20 @@
mod common;

use dotenvy::*;
use std::{env, error::Error, fs::File, result::Result};

use crate::common::*;

#[test]
fn test_from_read_override() -> Result<(), Box<dyn Error>> {
let dir = make_test_dotenv()?;

from_read_override(File::open(".env")?)?;

assert_eq!(env::var("TESTKEY")?, "test_val_overridden");
assert_eq!(env::var("EXISTING")?, "from_file");

env::set_current_dir(dir.path().parent().unwrap())?;
dir.close()?;
Ok(())
}
20 changes: 20 additions & 0 deletions dotenv/tests/test-from-read.rs
@@ -0,0 +1,20 @@
mod common;

use dotenvy::*;
use std::{env, error::Error, fs::File, result::Result};

use crate::common::*;

#[test]
fn test_from_read() -> Result<(), Box<dyn Error>> {
let dir = make_test_dotenv()?;

from_read(File::open(".env")?)?;

assert_eq!(env::var("TESTKEY")?, "test_val");
assert_eq!(env::var("EXISTING")?, "from_env");

env::set_current_dir(dir.path().parent().unwrap())?;
dir.close()?;
Ok(())
}