Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • libraries/envish
1 result
Show changes
Commits on Source (3)
[package]
name = "envish"
description = "A library for sourcing, reading, and interacting with .env files, including interpolation support"
version = "0.1.0"
version = "0.1.1"
edition = "2021"
[features]
......@@ -9,6 +9,6 @@ default = ["fs"]
fs = []
[dependencies]
nom = "7.1.3"
nom_locate = "4.2.0"
thiserror = "2.0.3"
nom = "7.1"
nom_locate = "4.2"
thiserror = "2.0"
use std::env;
use std::env::VarError;
use std::fmt::Display;
use crate::parser::{file, FileLine};
use nom_locate::LocatedSpan;
use std::str::FromStr;
......@@ -102,12 +103,60 @@ impl EnvironmentFile {
pub fn is_empty(&self) -> bool {
self.lines.is_empty()
}
}
#[derive(Clone, Debug, Default)]
pub struct ApplyOptions {
pub prefix: Option<String>,
pub overwrite: bool,
}
impl ApplyOptions {
pub fn new(prefix: impl Display, overwrite: bool) -> Self {
Self {
prefix: Some(prefix.to_string()),
overwrite,
}
}
pub fn apply(&self) -> Result<(), EnvironmentFileError> {
set_from_file(self)
pub fn with_prefix(prefix: impl Display) -> Self {
Self::new(prefix, false)
}
pub fn with_overwrite(overwrite: bool) -> Self {
Self {
prefix: None,
overwrite,
}
}
}
pub trait ApplyEnvironmentFile {
fn apply(&self, options: ApplyOptions);
}
impl ApplyEnvironmentFile for EnvironmentFile {
fn apply(&self, options: ApplyOptions) {
let _ = set_from_file(self, options);
}
}
impl <E> ApplyEnvironmentFile for Result<EnvironmentFile, E> {
fn apply(&self, options: ApplyOptions) {
if let Ok(file) = self {
file.apply(options);
}
}
}
impl ApplyEnvironmentFile for Option<EnvironmentFile> {
fn apply(&self, options: ApplyOptions) {
if let Some(file) = self {
file.apply(options);
}
}
}
pub struct EnvFileIterator<'a> {
lines: &'a [FileLine],
current: usize,
......@@ -166,14 +215,21 @@ fn is_missing_key(key: &str) -> Result<bool, EnvironmentFileError> {
}
}
fn set_from_file(file: &EnvironmentFile) -> Result<(), EnvironmentFileError> {
fn set_from_file(file: &EnvironmentFile, options: ApplyOptions) -> Result<(), EnvironmentFileError> {
let mut defferred = Vec::with_capacity(file.len());
for line in file.lines_kv() {
if let FileLine::KeyValue { key, .. } = &line {
if is_missing_key(key)? {
let key_name = if let Some(prefix) = &options.prefix {
format!("{}{}", prefix, key)
} else {
key.to_string()
};
if options.overwrite || is_missing_key(&key_name)? {
if line.is_complete() {
env::set_var(key, line.assemble_value());
eprintln!("Setting {} with value {}", key_name, line.assemble_value());
env::set_var(key_name, line.assemble_value());
} else {
defferred.push(line);
}
......@@ -183,7 +239,12 @@ fn set_from_file(file: &EnvironmentFile) -> Result<(), EnvironmentFileError> {
for line in defferred {
if let FileLine::KeyValue { key, .. } = line {
env::set_var(key, line.assemble_value());
let key_name = if let Some(prefix) = &options.prefix {
format!("{}{}", prefix, key)
} else {
key.to_string()
};
env::set_var(key_name, line.assemble_value());
}
}
......
......@@ -3,6 +3,7 @@ use std::fmt::Display;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use crate::env_file::{ApplyEnvironmentFile, ApplyOptions};
#[derive(Debug, thiserror::Error)]
#[allow(clippy::enum_variant_names)]
......@@ -31,17 +32,21 @@ pub fn env_file_from_path(path: impl AsRef<Path>) -> Result<EnvironmentFile, Env
}
pub fn dotenv() -> Result<(), EnvFsError> {
let file = env_file()?;
file.apply().map_err(|_| EnvFsError::EnvironmentError)
env_file()?.apply(Default::default());
Ok(())
}
pub fn dotenv_opts(options: ApplyOptions) -> Result<(), EnvFsError> {
env_file()?.apply(options);
Ok(())
}
pub fn dotenv_suffix(environment: impl Display) -> Result<(), EnvFsError> {
let file = env_file_suffix(environment)?;
file.apply().map_err(|_| EnvFsError::EnvironmentError)
env_file_suffix(environment)?.apply(Default::default());
Ok(())
}
pub fn dotenv_from(path: impl AsRef<Path>) -> Result<(), EnvFsError> {
let file = env_file_from_path(path)?;
file.apply().map_err(|_| EnvFsError::EnvironmentError)
env_file_from_path(path)?.apply(Default::default());
Ok(())
}
......@@ -5,8 +5,8 @@ mod env_file;
mod filesystem;
mod parser;
pub use env_file::{EnvironmentFile, EnvironmentFileError};
pub use env_file::{EnvironmentFile, EnvironmentFileError, ApplyEnvironmentFile, ApplyOptions};
pub use parser::{FileLine, ValuePart};
#[cfg(feature = "fs")]
pub use filesystem::{dotenv, dotenv_from, dotenv_suffix};
pub use filesystem::{dotenv, dotenv_from, dotenv_suffix, dotenv_opts};
......@@ -123,7 +123,7 @@ impl FileLine {
Self::KeyValue { key, value } => Self::KeyValue {
key: key.clone(),
value: value
.into_iter()
.iter()
.filter(|part| !matches!(part, ValuePart::Comment(..)))
.cloned()
.collect(),
......
use envish::EnvironmentFile;
use envish::{ApplyEnvironmentFile, ApplyOptions, EnvironmentFile};
#[test]
fn it_parses_basic_dotenv_file() {
......@@ -14,12 +14,13 @@ fn it_parses_basic_dotenv_file() {
std::env::var("SOME_OTHER_VARIABLE").expect_err("SOME_OTHER_VARIABLE should not be set");
let file = EnvironmentFile::parse(file_contents).expect("Failed to parse environment file");
file.apply().expect("Failed to apply environment file");
file.apply(Default::default());
assert_eq!(std::env::var("MY_BEST_VARIABLE").unwrap(), "some_value");
assert_eq!(std::env::var("SOME_OTHER_VARIABLE").unwrap(), "1234");
}
#[test]
fn it_parses_dotenv_file_with_interpolation() {
let file_contents = r#"
# This value won't be set in the test, and this comment will be ignored
......@@ -36,9 +37,33 @@ fn it_parses_dotenv_file_with_interpolation() {
std::env::var("INTERPOLATED_VARIABLE").expect_err("INTERPOLATED_VARIABLE should not be set");
let file = EnvironmentFile::parse(file_contents).expect("Failed to parse environment file");
file.apply().expect("Failed to apply environment file");
file.apply(Default::default());
assert_eq!(std::env::var("MY_BEST_VARIABLE").unwrap(), "some_value");
assert_eq!(std::env::var("SOME_OTHER_VARIABLE").unwrap(), "1234");
assert_eq!(std::env::var("INTERPOLATED_VARIABLE").unwrap(), "1234567");
}
#[test]
fn it_parses_dotenv_file_with_interpolation_and_prefix_option() {
let file_contents = r#"
# This value won't be set in the test, and this comment will be ignored
MY_BEST_VARIABLE=some_value
# This variable is also not defined, and it'll still be a string,
# because all environment variables are strings without being converted to other data types
SOME_OTHER_VARIABLE=1234
# This variable contains an interpolated value
INTERPOLATED_VARIABLE=${SOME_OTHER_VARIABLE}567
"#;
std::env::var("MY_BEST_VARIABLE").expect_err("MY_BEST_VARIABLE should not be set");
std::env::var("SOME_OTHER_VARIABLE").expect_err("SOME_OTHER_VARIABLE should not be set");
std::env::var("INTERPOLATED_VARIABLE").expect_err("INTERPOLATED_VARIABLE should not be set");
let file = EnvironmentFile::parse(file_contents).expect("Failed to parse environment file");
file.apply(ApplyOptions::with_prefix("APP_"));
assert_eq!(std::env::var("APP_MY_BEST_VARIABLE").unwrap(), "some_value");
assert_eq!(std::env::var("APP_SOME_OTHER_VARIABLE").unwrap(), "1234");
assert_eq!(std::env::var("APP_INTERPOLATED_VARIABLE").unwrap(), "1234567");
}
\ No newline at end of file