Skip to content
Snippets Groups Projects
Verified Commit 182e5a54 authored by Louis's avatar Louis :fire:
Browse files

Make 'apply' work with EnvFile and Result<EnvFile, T>

parent 3071390e
No related branches found
No related tags found
No related merge requests found
use std::env; use std::env;
use std::env::VarError; use std::env::VarError;
use std::fmt::Display;
use crate::parser::{file, FileLine}; use crate::parser::{file, FileLine};
use nom_locate::LocatedSpan; use nom_locate::LocatedSpan;
use std::str::FromStr; use std::str::FromStr;
...@@ -102,12 +103,52 @@ impl EnvironmentFile { ...@@ -102,12 +103,52 @@ impl EnvironmentFile {
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.lines.is_empty() self.lines.is_empty()
} }
}
pub fn apply(&self) -> Result<(), EnvironmentFileError> { #[derive(Clone, Debug, Default)]
set_from_file(self) 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 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);
}
}
}
pub struct EnvFileIterator<'a> { pub struct EnvFileIterator<'a> {
lines: &'a [FileLine], lines: &'a [FileLine],
current: usize, current: usize,
...@@ -166,14 +207,21 @@ fn is_missing_key(key: &str) -> Result<bool, EnvironmentFileError> { ...@@ -166,14 +207,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()); let mut defferred = Vec::with_capacity(file.len());
for line in file.lines_kv() { for line in file.lines_kv() {
if let FileLine::KeyValue { key, .. } = &line { 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() { 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 { } else {
defferred.push(line); defferred.push(line);
} }
...@@ -183,7 +231,12 @@ fn set_from_file(file: &EnvironmentFile) -> Result<(), EnvironmentFileError> { ...@@ -183,7 +231,12 @@ fn set_from_file(file: &EnvironmentFile) -> Result<(), EnvironmentFileError> {
for line in defferred { for line in defferred {
if let FileLine::KeyValue { key, .. } = line { 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; ...@@ -3,6 +3,7 @@ use std::fmt::Display;
use std::fs::File; use std::fs::File;
use std::io::Read; use std::io::Read;
use std::path::Path; use std::path::Path;
use crate::env_file::{ApplyEnvironmentFile, ApplyOptions};
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
#[allow(clippy::enum_variant_names)] #[allow(clippy::enum_variant_names)]
...@@ -31,17 +32,21 @@ pub fn env_file_from_path(path: impl AsRef<Path>) -> Result<EnvironmentFile, Env ...@@ -31,17 +32,21 @@ pub fn env_file_from_path(path: impl AsRef<Path>) -> Result<EnvironmentFile, Env
} }
pub fn dotenv() -> Result<(), EnvFsError> { pub fn dotenv() -> Result<(), EnvFsError> {
let file = env_file()?; env_file()?.apply(Default::default());
file.apply().map_err(|_| EnvFsError::EnvironmentError) Ok(())
}
pub fn dotenv_opts(options: ApplyOptions) -> Result<(), EnvFsError> {
env_file()?.apply(options);
Ok(())
} }
pub fn dotenv_suffix(environment: impl Display) -> Result<(), EnvFsError> { pub fn dotenv_suffix(environment: impl Display) -> Result<(), EnvFsError> {
let file = env_file_suffix(environment)?; env_file_suffix(environment)?.apply(Default::default());
file.apply().map_err(|_| EnvFsError::EnvironmentError) Ok(())
} }
pub fn dotenv_from(path: impl AsRef<Path>) -> Result<(), EnvFsError> { pub fn dotenv_from(path: impl AsRef<Path>) -> Result<(), EnvFsError> {
let file = env_file_from_path(path)?; env_file_from_path(path)?.apply(Default::default());
file.apply().map_err(|_| EnvFsError::EnvironmentError) Ok(())
} }
...@@ -5,8 +5,8 @@ mod env_file; ...@@ -5,8 +5,8 @@ mod env_file;
mod filesystem; mod filesystem;
mod parser; mod parser;
pub use env_file::{EnvironmentFile, EnvironmentFileError}; pub use env_file::{EnvironmentFile, EnvironmentFileError, ApplyEnvironmentFile, ApplyOptions};
pub use parser::{FileLine, ValuePart}; pub use parser::{FileLine, ValuePart};
#[cfg(feature = "fs")] #[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 { ...@@ -123,7 +123,7 @@ impl FileLine {
Self::KeyValue { key, value } => Self::KeyValue { Self::KeyValue { key, value } => Self::KeyValue {
key: key.clone(), key: key.clone(),
value: value value: value
.into_iter() .iter()
.filter(|part| !matches!(part, ValuePart::Comment(..))) .filter(|part| !matches!(part, ValuePart::Comment(..)))
.cloned() .cloned()
.collect(), .collect(),
......
use envish::EnvironmentFile; use envish::{ApplyEnvironmentFile, ApplyOptions, EnvironmentFile};
#[test] #[test]
fn it_parses_basic_dotenv_file() { fn it_parses_basic_dotenv_file() {
...@@ -14,12 +14,13 @@ 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"); 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"); 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("MY_BEST_VARIABLE").unwrap(), "some_value");
assert_eq!(std::env::var("SOME_OTHER_VARIABLE").unwrap(), "1234"); assert_eq!(std::env::var("SOME_OTHER_VARIABLE").unwrap(), "1234");
} }
#[test]
fn it_parses_dotenv_file_with_interpolation() { fn it_parses_dotenv_file_with_interpolation() {
let file_contents = r#" let file_contents = r#"
# This value won't be set in the test, and this comment will be ignored # 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() { ...@@ -36,9 +37,33 @@ fn it_parses_dotenv_file_with_interpolation() {
std::env::var("INTERPOLATED_VARIABLE").expect_err("INTERPOLATED_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"); 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("MY_BEST_VARIABLE").unwrap(), "some_value");
assert_eq!(std::env::var("SOME_OTHER_VARIABLE").unwrap(), "1234"); assert_eq!(std::env::var("SOME_OTHER_VARIABLE").unwrap(), "1234");
assert_eq!(std::env::var("INTERPOLATED_VARIABLE").unwrap(), "1234567"); 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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment