dotenvy-0.15.7/.cargo_vcs_info.json0000644000000001440000000000100126150ustar { "git": { "sha1": "e330110a7cd9064ec913e554c9fc325c9d7cdb88" }, "path_in_vcs": "dotenv" }dotenvy-0.15.7/Cargo.lock0000644000000222040000000000100105710ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi 0.1.19", "libc", "winapi", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "3.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ "atty", "bitflags", "clap_lex", "indexmap", "strsim", "termcolor", "textwrap", ] [[package]] name = "clap_lex" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" dependencies = [ "os_str_bytes", ] [[package]] name = "dotenvy" version = "0.15.7" dependencies = [ "clap", "once_cell", "tempfile", ] [[package]] name = "errno" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" dependencies = [ "errno-dragonfly", "libc", "winapi", ] [[package]] name = "errno-dragonfly" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", "libc", ] [[package]] name = "fastrand" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "hermit-abi" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" [[package]] name = "indexmap" version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", ] [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] [[package]] name = "io-lifetimes" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" dependencies = [ "hermit-abi 0.3.1", "libc", "windows-sys 0.45.0", ] [[package]] name = "libc" version = "0.2.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" [[package]] name = "linux-raw-sys" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "once_cell" version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "os_str_bytes" version = "6.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" [[package]] name = "redox_syscall" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] [[package]] name = "rustix" version = "0.36.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", "windows-sys 0.45.0", ] [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "tempfile" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" dependencies = [ "cfg-if", "fastrand", "redox_syscall", "rustix", "windows-sys 0.42.0", ] [[package]] name = "termcolor" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] [[package]] name = "textwrap" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows-sys" version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" dotenvy-0.15.7/Cargo.toml0000644000000026050000000000100106170ustar # 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 are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" rust-version = "1.56.1" name = "dotenvy" version = "0.15.7" authors = [ "Noemi Lapresta ", "Craig Hills ", "Mike Piccolo ", "Alice Maz ", "Sean Griffin ", "Adam Sharp ", "Arpad Borsos ", "Allan Zhang ", ] description = "A well-maintained fork of the dotenv crate" homepage = "https://github.com/allan2/dotenvy" readme = "README.md" keywords = [ "dotenv", "env", "environment", "settings", "config", ] license = "MIT" repository = "https://github.com/allan2/dotenvy" [[bin]] name = "dotenvy" required-features = ["cli"] [dependencies.clap] version = "3.2" optional = true [dev-dependencies.once_cell] version = "1.16.0" [dev-dependencies.tempfile] version = "3.3.0" [features] cli = ["clap"] dotenvy-0.15.7/Cargo.toml.orig000064400000000000000000000015561046102023000143040ustar 00000000000000[package] name = "dotenvy" version = "0.15.7" authors = [ "Noemi Lapresta ", "Craig Hills ", "Mike Piccolo ", "Alice Maz ", "Sean Griffin ", "Adam Sharp ", "Arpad Borsos ", "Allan Zhang ", ] description = "A well-maintained fork of the dotenv crate" homepage = "https://github.com/allan2/dotenvy" readme = "README.md" keywords = ["dotenv", "env", "environment", "settings", "config"] license = "MIT" repository = "https://github.com/allan2/dotenvy" edition = "2018" rust-version = "1.56.1" [[bin]] name = "dotenvy" required-features = ["cli"] [dependencies] clap = { version = "3.2", optional = true } [dev-dependencies] tempfile = "3.3.0" once_cell = "1.16.0" [features] cli = ["clap"] dotenvy-0.15.7/LICENSE000064400000000000000000000021161046102023000124130ustar 00000000000000# The MIT License (MIT) Copyright (c) 2014 Santiago Lapresta and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.dotenvy-0.15.7/README.md000064400000000000000000000064211046102023000126700ustar 00000000000000# dotenvy [![Crates.io](https://img.shields.io/crates/v/dotenvy.svg)](https://crates.io/crates/dotenvy) [![msrv 1.56.1](https://img.shields.io/badge/msrv-1.56.1-dea584.svg?logo=rust)](https://github.com/rust-lang/rust/releases/tag/1.56.1) [![ci](https://github.com/allan2/dotenvy/actions/workflows/ci.yml/badge.svg)](https://github.com/allan2/dotenvy/actions/workflows/ci.yml) [![docs](https://img.shields.io/docsrs/dotenvy?logo=docs.rs)](https://docs.rs/dotenvy/) A well-maintained fork of the [dotenv](https://github.com/dotenv-rs/dotenv) crate. This crate is the suggested alternative for `dotenv` in security advisory [RUSTSEC-2021-0141](https://rustsec.org/advisories/RUSTSEC-2021-0141.html). This library loads environment variables from a _.env_ file. This is convenient for dev environments. ## Components 1. [`dotenvy`](https://crates.io/crates/dotenvy) crate - A well-maintained fork of the `dotenv` crate. 2. [`dotenvy_macro`](https://crates.io/crates/dotenvy_macro) crate - A macro for compile time dotenv inspection. This is a fork of `dotenv_codegen`. 3. `dotenvy` CLI tool for running a command using the environment from a _.env_ file (currently Unix only) ## Usage ### Loading at runtime ```rust use std::env; use std::error::Error; fn main() -> Result<(), Box> { // Load environment variables from .env file. // Fails if .env file not found, not readable or invalid. dotenvy::dotenv()?; for (key, value) in env::vars() { println!("{key}: {value}"); } Ok(()) } ``` ### Loading at compile time The `dotenv!` macro provided by `dotenvy_macro` crate can be used. Warning: there is an outstanding issue with rust-analyzer ([rust-analyzer #9606](https://github.com/rust-analyzer/rust-analyzer/issues/9606)) related to the `dotenv!` macro ## Minimum supported Rust version Currently: **1.56.1** We aim to support the latest 8 rustc versions - approximately 1 year. Increasing MSRV is _not_ considered a semver-breaking change. ## Why does this fork exist? The original dotenv crate has not been updated since June 26, 2020. Attempts to reach the authors and present maintainer were not successful ([dotenv-rs/dotenv #74](https://github.com/dotenv-rs/dotenv/issues/74)). This fork intends to serve as the development home for the dotenv implementation in Rust. ## What are the differences from the original? This repo fixes: - more helpful errors for `dotenv!` ([dotenv-rs/dotenv #57](https://github.com/dotenv-rs/dotenv/pull/57)) It also adds: - multiline support for environment variable values - `io::Read` support via [`from_read`](https://docs.rs/dotenvy/latest/dotenvy/fn.from_read.html) and [`from_read_iter`](https://docs.rs/dotenvy/latest/dotenvy/fn.from_read_iter.html) - override support via [`dotenv_override`], [`from_filename_override`], [`from_path_override`] and [`from_read_override`] - improved docs For a full list of changes, refer to the [changelog](./CHANGELOG.md). ## The legend Legend has it that the Lost Maintainer will return, merging changes from `dotenvy` into `dotenv` with such thrust that all `Cargo.toml`s will lose one keystroke. Only then shall the Rust dotenv crateverse be united in true harmony. Until then, this repo dutifully carries on the dotenv torch. It is actively maintained. Contributions and PRs are very welcome! dotenvy-0.15.7/examples/list_variables.rs000064400000000000000000000003261046102023000165760ustar 00000000000000use dotenvy::{dotenv_iter, Error}; fn main() -> Result<(), Error> { dotenvy::dotenv()?; for item in dotenv_iter()? { let (key, val) = item?; println!("{}={}", key, val); } Ok(()) } dotenvy-0.15.7/src/bin/dotenvy.rs000064400000000000000000000035131046102023000150050ustar 00000000000000use clap::Arg; use std::os::unix::process::CommandExt; use std::process; macro_rules! die { ($fmt:expr) => ({ eprintln!($fmt); process::exit(1); }); ($fmt:expr, $($arg:tt)*) => ({ eprintln!($fmt, $($arg)*); process::exit(1); }); } fn make_command(name: &str, args: Vec<&str>) -> process::Command { let mut command = process::Command::new(name); for arg in args { command.arg(arg); } return command; } fn main() { let matches = clap::Command::new("dotenvy") .about("Run a command using the environment in a .env file") .override_usage("dotenvy [ARGS]...") .allow_external_subcommands(true) .arg_required_else_help(true) .arg( Arg::new("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 => dotenvy::dotenv(), Some(file) => dotenvy::from_filename(file), } .unwrap_or_else(|e| die!("error: failed to load environment: {}", e)); let mut command = match matches.subcommand() { Some((name, matches)) => { let args = matches .values_of("") .map(|v| v.collect()) .unwrap_or(Vec::new()); make_command(name, args) } None => die!("error: missing required argument "), }; if cfg!(target_os = "windows") { match command.spawn().and_then(|mut child| child.wait()) { Ok(status) => process::exit(status.code().unwrap_or(1)), Err(error) => die!("fatal: {}", error), }; } else { let error = command.exec(); die!("fatal: {}", error); }; } dotenvy-0.15.7/src/errors.rs000064400000000000000000000062211046102023000140600ustar 00000000000000use std::env; use std::error; use std::fmt; use std::io; pub type Result = std::result::Result; #[derive(Debug)] #[non_exhaustive] pub enum Error { LineParse(String, usize), Io(io::Error), EnvVar(env::VarError), } 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 ), } } } #[cfg(test)] mod test { use std::env; use std::error::Error as StdError; use std::io; use super::*; #[test] fn test_io_error_source() { let err = Error::Io(io::ErrorKind::PermissionDenied.into()); let io_err = err.source().unwrap().downcast_ref::().unwrap(); assert_eq!(io::ErrorKind::PermissionDenied, io_err.kind()); } #[test] fn test_envvar_error_source() { let err = Error::EnvVar(env::VarError::NotPresent); let var_err = err .source() .unwrap() .downcast_ref::() .unwrap(); assert_eq!(&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(io::ErrorKind::NotFound.into()); assert!(err.not_found()); } #[test] fn test_error_not_found_false() { let err = Error::Io(io::ErrorKind::PermissionDenied.into()); assert!(!err.not_found()); } #[test] fn test_io_error_display() { let err = Error::Io(io::ErrorKind::PermissionDenied.into()); let io_err: io::Error = 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(env::VarError::NotPresent); let var_err = 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 ); } } dotenvy-0.15.7/src/find.rs000064400000000000000000000026221046102023000134650ustar 00000000000000use 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", ))) } } dotenvy-0.15.7/src/iter.rs000064400000000000000000000144511046102023000135130ustar 00000000000000use std::collections::HashMap; use std::env; use std::io::prelude::*; use std::io::BufReader; use crate::errors::*; use crate::parse; pub struct Iter { lines: QuotedLines>, substitution_data: HashMap>, } impl Iter { pub fn new(reader: R) -> Iter { Iter { lines: QuotedLines { buf: BufReader::new(reader), }, substitution_data: HashMap::new(), } } /// 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()?; for item in self { let (key, value) = item?; if env::var(&key).is_err() { env::set_var(&key, value); } } Ok(()) } /// 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 { let (key, value) = item?; env::set_var(key, value); } Ok(()) } fn remove_bom(&mut self) -> Result<()> { let buffer = self.lines.buf.fill_buf().map_err(Error::Io)?; // https://www.compart.com/en/unicode/U+FEFF if buffer.starts_with(&[0xEF, 0xBB, 0xBF]) { // remove the BOM from the bufreader self.lines.buf.consume(3); } Ok(()) } } struct QuotedLines { buf: B, } enum ParseState { Complete, Escape, StrongOpen, StrongOpenEscape, WeakOpen, WeakOpenEscape, Comment, WhiteSpace, } fn eval_end_state(prev_state: ParseState, buf: &str) -> (usize, ParseState) { let mut cur_state = prev_state; let mut cur_pos: usize = 0; for (pos, c) in buf.char_indices() { cur_pos = pos; cur_state = match cur_state { ParseState::WhiteSpace => match c { '#' => return (cur_pos, ParseState::Comment), '\\' => ParseState::Escape, '"' => ParseState::WeakOpen, '\'' => ParseState::StrongOpen, _ => ParseState::Complete, }, ParseState::Escape => ParseState::Complete, ParseState::Complete => match c { c if c.is_whitespace() && c != '\n' && c != '\r' => ParseState::WhiteSpace, '\\' => ParseState::Escape, '"' => ParseState::WeakOpen, '\'' => ParseState::StrongOpen, _ => ParseState::Complete, }, ParseState::WeakOpen => match c { '\\' => ParseState::WeakOpenEscape, '"' => ParseState::Complete, _ => ParseState::WeakOpen, }, ParseState::WeakOpenEscape => ParseState::WeakOpen, ParseState::StrongOpen => match c { '\\' => ParseState::StrongOpenEscape, '\'' => ParseState::Complete, _ => ParseState::StrongOpen, }, ParseState::StrongOpenEscape => ParseState::StrongOpen, // Comments last the entire line. ParseState::Comment => panic!("should have returned early"), }; } (cur_pos, cur_state) } impl Iterator for QuotedLines { type Item = Result; fn next(&mut self) -> Option> { let mut buf = String::new(); let mut cur_state = ParseState::Complete; let mut buf_pos; let mut cur_pos; loop { buf_pos = buf.len(); match self.buf.read_line(&mut buf) { Ok(0) => match cur_state { ParseState::Complete => return None, _ => { let len = buf.len(); return Some(Err(Error::LineParse(buf, len))); } }, Ok(_n) => { // Skip lines which start with a # before iteration // This optimizes parsing a bit. if buf.trim_start().starts_with('#') { return Some(Ok(String::with_capacity(0))); } let result = eval_end_state(cur_state, &buf[buf_pos..]); cur_pos = result.0; cur_state = result.1; match cur_state { ParseState::Complete => { if buf.ends_with('\n') { buf.pop(); if buf.ends_with('\r') { buf.pop(); } } return Some(Ok(buf)); } ParseState::Escape | ParseState::StrongOpen | ParseState::StrongOpenEscape | ParseState::WeakOpen | ParseState::WeakOpenEscape | ParseState::WhiteSpace => {} ParseState::Comment => { buf.truncate(buf_pos + cur_pos); return Some(Ok(buf)); } } } Err(e) => return Some(Err(Error::Io(e))), } } } } 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(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)), } } } } dotenvy-0.15.7/src/lib.rs000064400000000000000000000256011046102023000133150ustar 00000000000000//! [`dotenv`]: https://crates.io/crates/dotenv //! A well-maintained fork of the [`dotenv`] crate //! //! This library loads environment variables from a *.env* file. This is convenient for dev environments. mod errors; mod find; mod iter; mod parse; use std::env::{self, Vars}; use std::ffi::OsStr; use std::fs::File; use std::io; use std::path::{Path, PathBuf}; use std::sync::Once; pub use crate::errors::*; use crate::find::Finder; pub use crate::iter::Iter; static START: Once = Once::new(); /// Gets the value for an environment variable. /// /// The value is `Ok(s)` if the environment variable is present and valid unicode. /// /// Note: this function gets values from any visible environment variable key, /// regardless of whether a *.env* file was loaded. /// /// # Examples: /// /// ```no_run /// # fn main() -> Result<(), Box> { /// let value = dotenvy::var("HOME")?; /// println!("{}", value); // prints `/home/foo` /// # Ok(()) /// # } /// ``` pub fn var>(key: K) -> Result { START.call_once(|| { dotenv().ok(); }); env::var(key).map_err(Error::EnvVar) } /// Returns an iterator of `(key, value)` pairs for all environment variables of the current process. /// The returned iterator contains a snapshot of the process's environment variables at the time of invocation. Modifications to environment variables afterwards will not be reflected. /// /// # Examples: /// /// ```no_run /// use std::io; /// /// let result: Vec<(String, String)> = dotenvy::vars().collect(); /// ``` pub fn vars() -> Vars { START.call_once(|| { dotenv().ok(); }); env::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 /// use std::path::Path; /// /// # fn main() -> Result<(), Box> { /// dotenvy::from_path(Path::new("path/to/.env"))?; /// # Ok(()) /// # } /// ``` pub fn from_path>(path: P) -> Result<()> { let iter = Iter::new(File::open(path).map_err(Error::Io)?); iter.load() } /// 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 std::path::Path; /// /// # fn main() -> Result<(), Box> { /// dotenvy::from_path_override(Path::new("path/to/.env"))?; /// # Ok(()) /// # } /// ``` pub fn from_path_override>(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 /// /// ```no_run /// use std::path::Path; /// /// # fn main() -> Result<(), Box> { /// for item in dotenvy::from_path_iter(Path::new("path/to/.env"))? { /// let (key, val) = item?; /// println!("{}={}", key, val); /// } /// # Ok(()) /// # } /// ``` pub fn from_path_iter>(path: P) -> Result> { Ok(Iter::new(File::open(path).map_err(Error::Io)?)) } /// 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 /// # fn main() -> Result<(), Box> { /// dotenvy::from_filename("custom.env")?; /// # Ok(()) /// # } /// ``` /// /// It is also possible to load from a typical *.env* file like so. However, using [`dotenv`] is preferred. /// /// ``` /// # fn main() -> Result<(), Box> { /// dotenvy::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) } /// 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 /// # fn main() -> Result<(), Box> { /// dotenvy::from_filename_override("custom.env")?; /// # Ok(()) /// # } /// ``` /// /// It is also possible to load from a typical *.env* file like so. However, using [`dotenv_override`] is preferred. /// /// ``` /// # fn main() -> Result<(), Box> { /// dotenvy::from_filename_override(".env")?; /// # Ok(()) /// # } /// ``` pub fn from_filename_override>(filename: P) -> Result { 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 /// /// ```no_run /// # fn main() -> Result<(), Box> { /// for item in dotenvy::from_filename_iter("custom.env")? { /// let (key, val) = item?; /// println!("{}={}", key, val); /// } /// # Ok(()) /// # } /// ``` pub fn from_filename_iter>(filename: P) -> Result> { let (_, iter) = Finder::new().filename(filename.as_ref()).find()?; Ok(iter) } /// Loads environment variables from [`io::Read`](std::io::Read). /// /// This is useful for loading environment variables from IPC or the network. /// /// 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 /// /// ```no_run /// # #![cfg(unix)] /// use std::io::Read; /// use std::os::unix::net::UnixStream; /// /// # fn main() -> Result<(), Box> { /// let mut stream = UnixStream::connect("/some/socket")?; /// dotenvy::from_read(stream)?; /// # Ok(()) /// # } /// ``` pub fn from_read(reader: R) -> Result<()> { let iter = Iter::new(reader); iter.load()?; Ok(()) } /// Loads environment variables from [`io::Read`](std::io::Read), /// overriding existing environment variables. /// /// This is useful for loading environment variables 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; /// /// # fn main() -> Result<(), Box> { /// let mut stream = UnixStream::connect("/some/socket")?; /// dotenvy::from_read_override(stream)?; /// # Ok(()) /// # } /// ``` pub fn from_read_override(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 /// /// ```no_run /// # #![cfg(unix)] /// use std::io::Read; /// use std::os::unix::net::UnixStream; /// /// # fn main() -> Result<(), Box> { /// let mut stream = UnixStream::connect("/some/socket")?; /// /// for item in dotenvy::from_read_iter(stream) { /// let (key, val) = item?; /// println!("{}={}", key, val); /// } /// # Ok(()) /// # } /// ``` pub fn from_read_iter(reader: R) -> Iter { 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 /// /// ``` /// # fn main() -> Result<(), Box> { /// dotenvy::dotenv()?; /// # Ok(()) /// # } /// ``` pub fn dotenv() -> Result { let (path, iter) = Finder::new().find()?; iter.load()?; Ok(path) } /// 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 /// ``` /// # fn main() -> Result<(), Box> { /// dotenvy::dotenv_override()?; /// # Ok(()) /// # } /// ``` pub fn dotenv_override() -> Result { let (path, iter) = Finder::new().find()?; iter.load_override()?; Ok(path) } /// Returns an iterator over environment variables. /// /// # Examples /// /// ``` /// # fn main() -> Result<(), Box> { /// for item in dotenvy::dotenv_iter()? { /// let (key, val) = item?; /// println!("{}={}", key, val); /// } /// # Ok(()) /// # } /// ``` pub fn dotenv_iter() -> Result> { let (_, iter) = Finder::new().find()?; Ok(iter) } dotenvy-0.15.7/src/parse.rs000064400000000000000000000436031046102023000136630ustar 00000000000000use std::collections::HashMap; use std::env; 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 { 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, self.substitution_data)?; self.substitution_data .insert(key.clone(), Some(parsed_value.clone())); 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) = 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_default()); }; } #[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.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; use std::env; 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.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() { 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() { 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 { panic!("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 { panic!("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 { panic!("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 { panic!("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 { panic!("Expected the second value not to be parsed") } } } dotenvy-0.15.7/tests/common/mod.rs000064400000000000000000000012001046102023000151560ustar 00000000000000use std::fs::File; use std::io::prelude::*; use std::{env, io}; use tempfile::{tempdir, TempDir}; pub fn tempdir_with_dotenv(dotenv_text: &str) -> io::Result { env::set_var("EXISTING", "from_env"); 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) } #[allow(dead_code)] pub fn make_test_dotenv() -> io::Result { tempdir_with_dotenv("TESTKEY=test_val\nTESTKEY=test_val_overridden\nEXISTING=from_file") } dotenvy-0.15.7/tests/integration/main.rs000064400000000000000000000000121046102023000163560ustar 00000000000000mod util; dotenvy-0.15.7/tests/integration/util/mod.rs000064400000000000000000000037231046102023000172020ustar 00000000000000#![allow(dead_code)] mod testenv; use std::env::{self, VarError}; pub use testenv::*; /// Default key used in envfile pub const TEST_KEY: &str = "TESTKEY"; /// Default value used in envfile pub const TEST_VALUE: &str = "test_val"; /// Default existing key set before test is run pub const TEST_EXISTING_KEY: &str = "TEST_EXISTING_KEY"; /// Default existing value set before test is run pub const TEST_EXISTING_VALUE: &str = "from_env"; /// Default overriding value in envfile pub const TEST_OVERRIDING_VALUE: &str = "from_file"; #[inline(always)] pub fn create_default_envfile() -> String { format!( "{}={}\n{}={}", TEST_KEY, TEST_VALUE, TEST_EXISTING_KEY, TEST_OVERRIDING_VALUE ) } /// missing equals #[inline(always)] pub fn create_invalid_envfile() -> String { format!( "{}{}\n{}{}", TEST_KEY, TEST_VALUE, TEST_EXISTING_KEY, TEST_OVERRIDING_VALUE ) } /// Assert that an environment variable is set and has the expected value. pub fn assert_env_var(key: &str, expected: &str) { match env::var(key) { Ok(actual) => assert_eq!( expected, actual, "\n\nFor Environment Variable `{}`:\n EXPECTED: `{}`\n ACTUAL: `{}`\n", key, expected, actual ), Err(VarError::NotPresent) => panic!("env var `{}` not found", key), Err(VarError::NotUnicode(val)) => panic!( "env var `{}` currently has invalid unicode: `{}`", key, val.to_string_lossy() ), } } /// Assert that an environment variable is not currently set. pub fn assert_env_var_unset(key: &str) { match env::var(key) { Ok(actual) => panic!( "env var `{}` should not be set, currently it is: `{}`", key, actual ), Err(VarError::NotUnicode(val)) => panic!( "env var `{}` should not be set, currently has invalid unicode: `{}`", key, val.to_string_lossy() ), _ => (), } } dotenvy-0.15.7/tests/integration/util/testenv.rs000064400000000000000000000254201046102023000201110ustar 00000000000000use once_cell::sync::OnceCell; use std::collections::HashMap; use std::io::Write; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex, PoisonError}; use std::{env, fs, io}; use tempfile::{tempdir, TempDir}; use super::*; /// Env var convenience type. type EnvMap = HashMap; /// Initialized in [`get_env_locker`] static ENV_LOCKER: OnceCell>> = OnceCell::new(); /// A test environment. /// /// Will create a new temporary directory. Use its builder methods to configure /// the directory structure, preset variables, envfile name and contents, and /// the working directory to run the test from. /// /// Creation methods: /// - [`TestEnv::init`]: blank environment (no envfile) /// - [`TestEnv::init_with_envfile`]: blank environment with an envfile /// - [`TestEnv::default`]: default testing environment (1 existing var and 2 /// set in a `.env` file) #[derive(Debug)] pub struct TestEnv { temp_dir: TempDir, work_dir: PathBuf, env_vars: Vec, envfile_contents: Option, envfile_path: PathBuf, } /// Simple key value struct for representing environment variables #[derive(Debug, Clone)] pub struct KeyVal { key: String, value: String, } /// Run a test closure within a test environment. /// /// Resets the environment variables, loads the [`TestEnv`], then runs the test /// closure. Ensures only one thread has access to the process environment. pub fn test_in_env(test_env: TestEnv, test: F) where F: FnOnce(), { let locker = get_env_locker(); // ignore a poisoned mutex // we expect some tests may panic to indicate a failure let original_env = locker.lock().unwrap_or_else(PoisonError::into_inner); // we reset the environment anyway upon acquiring the lock reset_env(&original_env); create_env(&test_env); test(); // drop the lock and the `TestEnv` - should delete the tempdir } /// Run a test closure within the default test environment. /// /// Resets the environment variables, creates the default [`TestEnv`], then runs /// the test closure. Ensures only one thread has access to the process /// environment. /// /// The default testing environment sets an existing environment variable /// `TEST_EXISTING_KEY`, which is set to `from_env`. It also creates a `.env` /// file with the two lines: /// /// ```ini /// TESTKEY=test_val /// TEST_EXISTING_KEY=from_file /// ``` /// /// Notice that file has the potential to override `TEST_EXISTING_KEY` depending /// on the what's being tested. pub fn test_in_default_env(test: F) where F: FnOnce(), { let test_env = TestEnv::default(); test_in_env(test_env, test); } impl TestEnv { /// Blank testing environment in a new temporary directory. /// /// No envfile_contents or pre-existing variables to set. The envfile_name /// is set to `.env` but wont be written until its contents is set. The /// working directory is the created temporary directory. pub fn init() -> Self { let tempdir = tempdir().expect("create tempdir"); let work_dir = tempdir.path().to_owned(); let envfile_path = work_dir.join(".env"); Self { temp_dir: tempdir, work_dir, env_vars: Default::default(), envfile_contents: None, envfile_path, } } /// Testing environment with custom envfile_contents. /// /// No pre-existing env_vars set. The envfile_name is set to `.env`. The /// working directory is the created temporary directory. pub fn init_with_envfile(contents: impl ToString) -> Self { let mut test_env = Self::init(); test_env.set_envfile_contents(contents); test_env } /// Change the name of the default `.env` file. /// /// It will still be placed in the root temporary directory. If you need to /// put the envfile in a different directory, use /// [`set_envfile_path`](TestEnv::set_envfile_path) instead. pub fn set_envfile_name(&mut self, name: impl AsRef) -> &mut Self { self.envfile_path = self.temp_path().join(name); self } /// Change the absolute path to the envfile. pub fn set_envfile_path(&mut self, path: PathBuf) -> &mut Self { self.envfile_path = path; self } /// Specify the contents of the envfile. /// /// If this is the only change to the [`TestEnv`] being made, use /// [`new_with_envfile`](TestEnv::new_with_envfile). /// /// Setting it to an empty string will cause an empty envfile to be created pub fn set_envfile_contents(&mut self, contents: impl ToString) -> &mut Self { self.envfile_contents = Some(contents.to_string()); self } /// Set the working directory the test will run from. /// /// The default is the created temporary directory. This method is useful if /// you wish to run a test from a subdirectory or somewhere else. pub fn set_work_dir(&mut self, path: PathBuf) -> &mut Self { self.work_dir = path; self } /// Add an individual environment variable. /// /// This adds more pre-existing environment variables to the process before /// any tests are run. pub fn add_env_var(&mut self, key: impl ToString, value: impl ToString) -> &mut Self { self.env_vars.push(KeyVal { key: key.to_string(), value: value.to_string(), }); self } /// Set the pre-existing environment variables. /// /// These variables will get added to the process' environment before the /// test is run. This overrides any previous env vars added to the /// [`TestEnv`]. /// /// If you wish to just use a slice of tuples, use /// [`set_env_vars_tuple`](TestEnv::set_env_vars_tuple) instead. pub fn set_env_vars(&mut self, env_vars: Vec) -> &mut Self { self.env_vars = env_vars; self } /// Set the pre-existing environment variables using [`str`] tuples. /// /// These variables will get added to the process' environment before the /// test is run. This overrides any previous env vars added to the /// [`TestEnv`]. /// /// If you wish to add an owned `Vec` instead of `str` tuples, use /// [`set_env_vars`](TestEnv::set_env_vars) instead. pub fn set_env_vars_tuple(&mut self, env_vars: &[(&str, &str)]) -> &mut Self { self.env_vars = env_vars .iter() .map(|(key, value)| KeyVal { key: key.to_string(), value: value.to_string(), }) .collect(); self } /// Create a child folder within the temporary directory. /// /// This will not change the working directory the test is run in, or where /// the envfile is created. /// /// Will create parent directories if they are missing. pub fn add_child_dir_all(&self, rel_path: impl AsRef) -> PathBuf { let rel_path = rel_path.as_ref(); let child_dir = self.temp_path().join(rel_path); if let Err(err) = fs::create_dir_all(&child_dir) { panic!( "unable to create child directory: `{}` in `{}`: {}", self.temp_path().display(), rel_path.display(), err ); } child_dir } /// Get a reference to the path of the temporary directory. pub fn temp_path(&self) -> &Path { self.temp_dir.path() } /// Get a reference to the working directory the test will be run from. pub fn work_dir(&self) -> &Path { &self.work_dir } /// Get a reference to environnement variables that will be set **before** /// the test. pub fn env_vars(&self) -> &[KeyVal] { &self.env_vars } /// Get a reference to the string that will be placed in the envfile. /// /// If `None` is returned, an envfile will not be created pub fn envfile_contents(&self) -> Option<&str> { self.envfile_contents.as_deref() } /// Get a reference to the path of the envfile. pub fn envfile_path(&self) -> &Path { &self.envfile_path } } impl Default for TestEnv { fn default() -> Self { let temp_dir = tempdir().expect("create tempdir"); let work_dir = temp_dir.path().to_owned(); let env_vars = vec![KeyVal { key: TEST_EXISTING_KEY.into(), value: TEST_EXISTING_VALUE.into(), }]; let envfile_contents = Some(create_default_envfile()); let envfile_path = work_dir.join(".env"); Self { temp_dir, work_dir, env_vars, envfile_contents, envfile_path, } } } impl From<(&str, &str)> for KeyVal { fn from(kv: (&str, &str)) -> Self { let (key, value) = kv; Self { key: key.to_string(), value: value.to_string(), } } } impl From<(String, String)> for KeyVal { fn from(kv: (String, String)) -> Self { let (key, value) = kv; Self { key, value } } } /// Get a guarded copy of the original process' env vars. fn get_env_locker() -> Arc> { Arc::clone(ENV_LOCKER.get_or_init(|| { let map: EnvMap = env::vars().collect(); Arc::new(Mutex::new(map)) })) } /// Reset the process' env vars back to what was in `original_env`. fn reset_env(original_env: &EnvMap) { // remove keys if they weren't in the original environment env::vars() .filter(|(key, _)| !original_env.contains_key(key)) .for_each(|(key, _)| env::remove_var(key)); // ensure original keys have their original values original_env .iter() .for_each(|(key, value)| env::set_var(key, value)); } /// Create an environment to run tests in. /// /// Writes the envfile, sets the working directory, and sets environment vars. fn create_env(test_env: &TestEnv) { // only create the envfile if its contents has been set if let Some(contents) = test_env.envfile_contents() { create_envfile(&test_env.envfile_path, contents); } env::set_current_dir(&test_env.work_dir).expect("setting working directory"); for KeyVal { key, value } in &test_env.env_vars { env::set_var(key, value) } } /// Create an envfile for use in tests. fn create_envfile(path: &Path, contents: &str) { if path.exists() { panic!("envfile `{}` already exists", path.display()) } // inner function to group together io::Results fn create_env_file_inner(path: &Path, contents: &str) -> io::Result<()> { let mut file = fs::File::create(path)?; file.write_all(contents.as_bytes())?; file.sync_all() } // call inner function if let Err(err) = create_env_file_inner(path, contents) { // handle any io::Result::Err panic!("error creating envfile `{}`: {}", path.display(), err); } } dotenvy-0.15.7/tests/test-child-dir.rs000064400000000000000000000006451046102023000157370ustar 00000000000000mod common; use dotenvy::*; use std::{env, error::Error, fs, result::Result}; use crate::common::*; #[test] fn test_child_dir() -> Result<(), Box> { let dir = make_test_dotenv()?; fs::create_dir("child")?; env::set_current_dir("child")?; dotenv()?; assert_eq!(env::var("TESTKEY")?, "test_val"); env::set_current_dir(dir.path().parent().unwrap())?; dir.close()?; Ok(()) } dotenvy-0.15.7/tests/test-default-location-override.rs000064400000000000000000000006671046102023000211530ustar 00000000000000mod common; use std::{env, error::Error, result::Result}; use dotenvy::*; use crate::common::*; #[test] fn test_default_location_override() -> Result<(), Box> { 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(()) } dotenvy-0.15.7/tests/test-default-location.rs000064400000000000000000000006311046102023000173250ustar 00000000000000mod common; use dotenvy::*; use std::{env, error::Error, result::Result}; use crate::common::*; #[test] fn test_default_location() -> Result<(), Box> { 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()?; Ok(()) } dotenvy-0.15.7/tests/test-dotenv-iter.rs000064400000000000000000000006551046102023000163410ustar 00000000000000mod common; use dotenvy::*; use std::{env, error::Error, result::Result}; use crate::common::*; #[test] fn test_dotenv_iter() -> Result<(), Box> { let dir = make_test_dotenv()?; let iter = dotenv_iter()?; assert!(env::var("TESTKEY").is_err()); iter.load()?; assert_eq!(env::var("TESTKEY")?, "test_val"); env::set_current_dir(dir.path().parent().unwrap())?; dir.close()?; Ok(()) } dotenvy-0.15.7/tests/test-from-filename-iter.rs000064400000000000000000000007131046102023000175560ustar 00000000000000mod common; use dotenvy::*; use std::{env, error::Error, result::Result}; use crate::common::*; #[test] fn test_from_filename_iter() -> Result<(), Box> { let dir = make_test_dotenv()?; let iter = from_filename_iter(".env")?; assert!(env::var("TESTKEY").is_err()); iter.load()?; assert_eq!(env::var("TESTKEY").unwrap(), "test_val"); env::set_current_dir(dir.path().parent().unwrap())?; dir.close()?; Ok(()) } dotenvy-0.15.7/tests/test-from-filename-override.rs000064400000000000000000000007001046102023000204260ustar 00000000000000mod common; use dotenvy::*; use std::{env, error::Error, result::Result}; use crate::common::*; #[test] fn test_from_filename_override() -> Result<(), Box> { 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(()) } dotenvy-0.15.7/tests/test-from-filename.rs000064400000000000000000000006421046102023000166160ustar 00000000000000mod common; use dotenvy::*; use std::{env, error::Error, result::Result}; use crate::common::*; #[test] fn test_from_filename() -> Result<(), Box> { let dir = make_test_dotenv()?; 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()?; Ok(()) } dotenvy-0.15.7/tests/test-from-path-iter.rs000064400000000000000000000007721046102023000167370ustar 00000000000000mod common; use dotenvy::*; use std::{env, error::Error, result::Result}; use crate::common::*; #[test] fn test_from_path_iter() -> Result<(), Box> { let dir = make_test_dotenv()?; let mut path = env::current_dir()?; path.push(".env"); let iter = from_path_iter(&path)?; assert!(env::var("TESTKEY").is_err()); iter.load()?; assert_eq!(env::var("TESTKEY")?, "test_val"); env::set_current_dir(dir.path().parent().unwrap())?; dir.close()?; Ok(()) } dotenvy-0.15.7/tests/test-from-path-override.rs000064400000000000000000000007661046102023000176160ustar 00000000000000mod common; use crate::common::*; use dotenvy::*; use std::{env, error::Error, result::Result}; #[test] fn test_from_path_override() -> Result<(), Box> { 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(()) } dotenvy-0.15.7/tests/test-from-path.rs000064400000000000000000000007301046102023000157700ustar 00000000000000mod common; use crate::common::*; use dotenvy::*; use std::{env, error::Error, result::Result}; #[test] fn test_from_path() -> Result<(), Box> { let dir = make_test_dotenv()?; let mut path = env::current_dir()?; path.push(".env"); 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()?; Ok(()) } dotenvy-0.15.7/tests/test-from-read-override.rs000064400000000000000000000007171046102023000175710ustar 00000000000000mod common; use dotenvy::*; use std::{env, error::Error, fs::File, result::Result}; use crate::common::*; #[test] fn test_from_read_override() -> Result<(), Box> { 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(()) } dotenvy-0.15.7/tests/test-from-read.rs000064400000000000000000000006611046102023000157520ustar 00000000000000mod common; use dotenvy::*; use std::{env, error::Error, fs::File, result::Result}; use crate::common::*; #[test] fn test_from_read() -> Result<(), Box> { 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(()) } dotenvy-0.15.7/tests/test-ignore-bom.rs000064400000000000000000000007461046102023000161400ustar 00000000000000mod common; use crate::common::*; use dotenvy::*; use std::{env, error::Error, result::Result}; #[test] fn test_ignore_bom() -> Result<(), Box> { let bom = "\u{feff}"; let dir = tempdir_with_dotenv(&format!("{}TESTKEY=test_val", bom))?; let mut path = env::current_dir()?; path.push(".env"); from_path(&path)?; assert_eq!(env::var("TESTKEY")?, "test_val"); env::set_current_dir(dir.path().parent().unwrap())?; dir.close()?; Ok(()) } dotenvy-0.15.7/tests/test-multiline-comment.rs000064400000000000000000000023651046102023000175430ustar 00000000000000mod common; use std::env; use common::tempdir_with_dotenv; use dotenvy::dotenv; #[test] fn test_issue_12() { let _f = tempdir_with_dotenv( r#" # Start of .env file # Comment line with single ' quote # Comment line with double " quote # Comment line with double " quote and starts with a space TESTKEY1=test_val # 1 '" comment TESTKEY2=test_val_with_#_hash # 2 '" comment TESTKEY3="test_val quoted with # hash" # 3 '" comment TESTKEY4="Line 1 # Line 2 Line 3" # 4 Multiline "' comment TESTKEY5="Line 4 # Line 5 Line 6 " # 5 Multiline "' comment # End of .env file "#, ) .expect("should write test env"); dotenv().expect("should succeed"); assert_eq!( env::var("TESTKEY1").expect("testkey1 env key not set"), "test_val" ); assert_eq!( env::var("TESTKEY2").expect("testkey2 env key not set"), "test_val_with_#_hash" ); assert_eq!( env::var("TESTKEY3").expect("testkey3 env key not set"), "test_val quoted with # hash" ); assert_eq!( env::var("TESTKEY4").expect("testkey4 env key not set"), r#"Line 1 # Line 2 Line 3"# ); assert_eq!( env::var("TESTKEY5").expect("testkey5 env key not set"), r#"Line 4 # Line 5 Line 6 "# ); } dotenvy-0.15.7/tests/test-multiline.rs000064400000000000000000000017031046102023000160760ustar 00000000000000mod common; use crate::common::*; use dotenvy::*; use std::{env, error::Error, result::Result}; #[test] fn test_multiline() -> Result<(), Box> { let value = "-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----\\n\\\"QUOTED\\\""; let weak = "-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----\n\"QUOTED\""; let dir = tempdir_with_dotenv(&format!( r#" KEY=my\ cool\ value KEY3="awesome \"stuff\" more on other lines" KEY4='hello '\''world'" good ' \'morning" WEAK="{}" STRONG='{}' "#, value, value ))?; dotenv()?; assert_eq!(var("KEY")?, r#"my cool value"#); assert_eq!( var("KEY3")?, r#"awesome "stuff" more on other lines"# ); assert_eq!( var("KEY4")?, r#"hello 'world good ' 'morning"# ); assert_eq!(var("WEAK")?, weak); assert_eq!(var("STRONG")?, value); env::set_current_dir(dir.path().parent().unwrap())?; dir.close()?; Ok(()) } dotenvy-0.15.7/tests/test-var.rs000064400000000000000000000005031046102023000146610ustar 00000000000000mod common; use dotenvy::*; use std::{env, error::Error, result::Result}; use crate::common::*; #[test] fn test_var() -> Result<(), Box> { let dir = make_test_dotenv()?; assert_eq!(var("TESTKEY")?, "test_val"); env::set_current_dir(dir.path().parent().unwrap())?; dir.close()?; Ok(()) } dotenvy-0.15.7/tests/test-variable-substitution.rs000064400000000000000000000027231046102023000204360ustar 00000000000000mod common; use dotenvy::*; use std::{env, error::Error, result::Result}; use crate::common::*; #[test] fn test_variable_substitutions() -> Result<(), Box> { 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 ))?; assert_eq!(var("KEY")?, "value"); assert_eq!(var("KEY1")?, "value1"); assert_eq!(var("KEY_U")?, "value+valueU"); assert_eq!(var("SUBSTITUTION_FOR_STRONG_QUOTES")?, common_string); assert_eq!( var("SUBSTITUTION_FOR_WEAK_QUOTES")?, [ "", "value", "value1", "value1", "value_U", "value+valueU", "$KEY" ] .join(">>") ); assert_eq!( var("SUBSTITUTION_WITHOUT_QUOTES")?, [ "", "value", "value1", "value1", "value_U", "value+valueU", "$KEY" ] .join(">>") ); env::set_current_dir(dir.path().parent().unwrap())?; dir.close()?; Ok(()) } dotenvy-0.15.7/tests/test-vars.rs000064400000000000000000000006261046102023000150520ustar 00000000000000mod common; use std::{collections::HashMap, env, error::Error, result::Result}; use dotenvy::*; use crate::common::*; #[test] fn test_vars() -> Result<(), Box> { let dir = make_test_dotenv()?; let vars: HashMap = vars().collect(); assert_eq!(vars["TESTKEY"], "test_val"); env::set_current_dir(dir.path().parent().unwrap())?; dir.close()?; Ok(()) }