diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 00000000..f5b4211c --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,50 @@ +name: CI + +on: + push: + branches: + master + pull_request: + branches: + master + +jobs: + test: + name: Test + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + rust: [stable, beta, nightly] + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + override: true + - uses: actions-rs/cargo@v1 + with: + command: test + check: + runs-on: ubuntu-latest + name: Check + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: -- --check + + - uses: actions-rs/tarpaulin@v0.1 + with: + args: --ignore-tests -- --test-threads 1 + - uses: codecov/codecov-action@v1 + - name: Archive code coverage results + uses: actions/upload-artifact@v1 + with: + name: code-coverage-report + path: cobertura.xml diff --git a/README.md b/README.md index 4e05c2ab..1f91ab39 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # rust-dotenv -[![Build Status](https://dev.azure.com/dotenv-rs/dotenv/_apis/build/status/dotenv-rs.dotenv?branchName=master)](https://dev.azure.com/dotenv-rs/dotenv/_build/latest?definitionId=2&branchName=master) +![CI](https://github.com/dotenv-rs/dotenv/workflows/CI/badge.svg) [![codecov](https://codecov.io/gh/dotenv-rs/dotenv/branch/master/graph/badge.svg)](https://codecov.io/gh/dotenv-rs/dotenv) [![Crates.io](https://img.shields.io/crates/v/dotenv.svg)](https://crates.io/crates/dotenv) diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index f3b99df6..00000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,22 +0,0 @@ -trigger: - branches: - include: ["master", "v*"] - paths: - exclude: ["*.md"] - -jobs: -- template: azure-pipelines/build-template.yml - parameters: - name: Windows - poolName: Hosted VS2017 - -- template: azure-pipelines/build-template.yml - parameters: - name: MacOs - poolName: Hosted macOS - - -- template: azure-pipelines/build-template.yml - parameters: - name: Linux - poolName: Hosted Ubuntu 1604 \ No newline at end of file diff --git a/azure-pipelines/build-template.yml b/azure-pipelines/build-template.yml deleted file mode 100644 index db059cb3..00000000 --- a/azure-pipelines/build-template.yml +++ /dev/null @@ -1,68 +0,0 @@ -jobs: -- job: ${{ parameters.name }} - pool: ${{ parameters.poolName }} - strategy: - matrix: - nightly: - RUSTUP_TOOLCHAIN: nightly - beta: - RUSTUP_TOOLCHAIN: beta - stable: - RUSTUP_TOOLCHAIN: stable - steps: - - ${{ if ne(parameters.name, 'Windows') }}: - # Linux and macOS. - - script: | - curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $RUSTUP_TOOLCHAIN - echo "##vso[task.setvariable variable=PATH;]$PATH:$HOME/.cargo/bin" - displayName: Install rust - - ${{ if eq(parameters.name, 'Windows') }}: - # Windows. - - script: | - curl -sSf -o rustup-init.exe https://win.rustup.rs - rustup-init.exe -y --default-toolchain %RUSTUP_TOOLCHAIN% - set PATH=%PATH%;%USERPROFILE%\.cargo\bin - echo "##vso[task.setvariable variable=PATH;]%PATH%;%USERPROFILE%\.cargo\bin" - displayName: Install rust (windows) - # All platforms. - - script: | - rustc -Vv - cargo -V - echo $RUSTUP_TOOLCHAIN - displayName: Query rust and cargo versions - - script: | - sudo apt-get update - sudo apt-get install cmake g++ pkg-config jq - sudo apt-get install libcurl4-openssl-dev libelf-dev libdw-dev binutils-dev libiberty-dev - cargo install cargo-kcov - cargo kcov --print-install-kcov-sh | sh - displayName: Install kcov - condition: and(eq(variables['Agent.OS'], 'Linux'), eq(variables['Agent.JobName'], 'Linux stable')) - - script: cargo install cargo2junit - displayName: Install cargo junit formatter - - script: cargo build - displayName: Build - - script: cargo test -- -Z unstable-options --format json | cargo2junit > testResults.xml - displayName: Build and run tests - - task: PublishTestResults@2 - inputs: - testResultsFormat: 'JUnit' - testResultsFiles: '**/testResults.xml' - testRunTitle: $(Agent.JobName) tests - - script: | - mkdir coverageReport - cargo kcov -o coverageReport --all - cd coverageReport/kcov-merged - python ../../azure-pipelines/fix_coverage_for_cobertura.py - displayName: Run code coverage - condition: and(eq(variables['Agent.OS'], 'Linux'), eq(variables['Agent.JobName'], 'Linux stable')) - - script: bash <(curl -s https://codecov.io/bash) - env: - CODECOV_TOKEN: $(CODECOV_TOKEN_SECRET) - displayName: Publish to codecov.io - condition: and(eq(variables['Agent.OS'], 'Linux'), eq(variables['Agent.JobName'], 'Linux stable')) - - task: PublishCodeCoverageResults@1 - inputs: - codeCoverageTool: 'cobertura' - summaryFileLocation: $(System.DefaultWorkingDirectory)/**/coverageReport/kcov-merged/cobertura.xml - condition: and(eq(variables['Agent.OS'], 'Linux'), eq(variables['Agent.JobName'], 'Linux stable')) diff --git a/azure-pipelines/fix_coverage_for_cobertura.py b/azure-pipelines/fix_coverage_for_cobertura.py deleted file mode 100644 index 6b3e174b..00000000 --- a/azure-pipelines/fix_coverage_for_cobertura.py +++ /dev/null @@ -1,77 +0,0 @@ -''' -Created on Aug 3, 2016 - -@author: YLin2 -''' - -import sys -import os -from xml.dom import minidom - -def fix_class(class_node): - valid_lines = 0 - covered_lines = 0 - for lines_node in class_node.getElementsByTagName('lines'): - for line in lines_node.getElementsByTagName('line'): - if not line.hasAttribute('hits'): - continue - valid_lines += 1 - hit = line.getAttribute('hits') - if hit == '1': - covered_lines += 1 - if valid_lines > 0: - class_node.setAttribute('line-rate', repr(float(covered_lines)/float(valid_lines))) - return valid_lines, covered_lines - - -def fix_package(package_node): - valid_lines = 0 - covered_lines = 0 - for classes_node in package_node.getElementsByTagName('classes'): - for class_node in classes_node.getElementsByTagName('class'): - current_valid_lines, current_covered_lines = fix_class(class_node) - valid_lines += current_valid_lines - covered_lines += current_covered_lines - if valid_lines > 0: - package_node.setAttribute('line-rate', repr(float(covered_lines)/float(valid_lines))) - return valid_lines, covered_lines - - -def fix(*args, **kargs): - default_file_path = '' - default_file_name = 'cobertura.xml' - if len(args[0]) > 1: - arg = args[0][1] - else: - arg = default_file_path - - if os.path.isdir(arg): - file_name = os.path.join(arg, default_file_name) - else: - file_name = os.path.join(default_file_path, default_file_name) - - print 'processing: '+file_name - xml_file = open(file_name, 'r') - xml_doc = minidom.parse(xml_file) - xml_file.close() - xml_root = xml_doc.documentElement - original_copy = open('coverage.original.xml', 'w') - xml_root.writexml(original_copy) - valid_lines = 0 - covered_lines = 0 - tag_valid_lines = 'lines-valid' - tag_covered_lines = 'lines-covered' - - for package_node in xml_doc.getElementsByTagName('package'): - current_valid_lines, current_covered_lines = fix_package(package_node) - valid_lines += current_valid_lines - covered_lines += current_covered_lines - - xml_root.setAttribute(tag_valid_lines, repr(valid_lines)) - xml_root.setAttribute(tag_covered_lines, repr(covered_lines)) - fixed_copy = open(os.path.basename(file_name), 'w') - xml_root.writexml(fixed_copy) - -if __name__ == '__main__': - fix(sys.argv) - diff --git a/dotenv/src/bin/dotenv.rs b/dotenv/src/bin/dotenv.rs index 5af9a6bb..dce09c6d 100644 --- a/dotenv/src/bin/dotenv.rs +++ b/dotenv/src/bin/dotenv.rs @@ -3,7 +3,7 @@ extern crate dotenv; use clap::{App, AppSettings, Arg}; use std::os::unix::process::CommandExt; -use std::process::{Command, exit}; +use std::process::{exit, Command}; macro_rules! die { ($fmt:expr) => ({ @@ -33,26 +33,30 @@ fn main() { .setting(AppSettings::AllowExternalSubcommands) .setting(AppSettings::ArgRequiredElseHelp) .setting(AppSettings::UnifiedHelpMessage) - .arg(Arg::with_name("FILE") - .short("f") - .long("file") - .takes_value(true) - .help("Use a specific .env file (defaults to .env)")) + .arg( + Arg::with_name("FILE") + .short("f") + .long("file") + .takes_value(true) + .help("Use a specific .env file (defaults to .env)"), + ) .get_matches(); match matches.value_of("FILE") { None => dotenv::dotenv(), Some(file) => dotenv::from_filename(file), - }.unwrap_or_else(|e| die!("error: failed to load environment: {}", e)); + } + .unwrap_or_else(|e| die!("error: failed to load environment: {}", e)); let mut command = match matches.subcommand() { (name, Some(matches)) => { - let args = matches.values_of("") + let args = matches + .values_of("") .map(|v| v.collect()) .unwrap_or(Vec::new()); make_command(name, args) - }, + } _ => die!("error: missing required argument "), }; diff --git a/dotenv/src/errors.rs b/dotenv/src/errors.rs index bcce24b8..a8df8449 100644 --- a/dotenv/src/errors.rs +++ b/dotenv/src/errors.rs @@ -1,6 +1,6 @@ -use std::io; -use std::fmt; use std::error; +use std::fmt; +use std::io; pub type Result = std::result::Result; @@ -10,7 +10,7 @@ pub enum Error { Io(io::Error), EnvVar(std::env::VarError), #[doc(hidden)] - __Nonexhaustive + __Nonexhaustive, } impl Error { @@ -37,7 +37,11 @@ impl fmt::Display for Error { 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), + Error::LineParse(line, error_index) => write!( + fmt, + "Error parsing line: '{}', error at line index: {}", + line, error_index + ), _ => unreachable!(), } } @@ -52,14 +56,22 @@ mod test { #[test] fn test_io_error_source() { let err = Error::Io(std::io::ErrorKind::PermissionDenied.into()); - let io_err = err.source().unwrap().downcast_ref::().unwrap(); + let io_err = err + .source() + .unwrap() + .downcast_ref::() + .unwrap(); assert_eq!(std::io::ErrorKind::PermissionDenied, io_err.kind()); } #[test] fn test_envvar_error_source() { let err = Error::EnvVar(std::env::VarError::NotPresent); - let var_err = err.source().unwrap().downcast_ref::().unwrap(); + let var_err = err + .source() + .unwrap() + .downcast_ref::() + .unwrap(); assert_eq!(&std::env::VarError::NotPresent, var_err); } @@ -105,6 +117,9 @@ mod 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); + assert_eq!( + "Error parsing line: 'test line', error at line index: 2", + err_desc + ); } -} \ No newline at end of file +} diff --git a/dotenv/src/find.rs b/dotenv/src/find.rs index ab44e829..dd607289 100644 --- a/dotenv/src/find.rs +++ b/dotenv/src/find.rs @@ -6,7 +6,7 @@ use crate::errors::*; use crate::iter::Iter; pub struct Finder<'a> { - filename: &'a Path, + filename: &'a Path, } impl<'a> Finder<'a> { @@ -34,9 +34,11 @@ 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); - }, + Ok(metadata) => { + if metadata.is_file() { + return Ok(candidate); + } + } Err(error) => { if error.kind() != io::ErrorKind::NotFound { return Err(Error::Io(error)); @@ -47,6 +49,9 @@ pub fn find(directory: &Path, filename: &Path) -> Result { if let Some(parent) = directory.parent() { find(parent, filename) } else { - Err(Error::Io(io::Error::new(io::ErrorKind::NotFound, "path not found"))) + Err(Error::Io(io::Error::new( + io::ErrorKind::NotFound, + "path not found", + ))) } } diff --git a/dotenv/src/iter.rs b/dotenv/src/iter.rs index f38b4476..40506e2d 100644 --- a/dotenv/src/iter.rs +++ b/dotenv/src/iter.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::env; -use std::io::{BufReader, Lines}; use std::io::prelude::*; +use std::io::{BufReader, Lines}; use crate::errors::*; use crate::parse; diff --git a/dotenv/src/lib.rs b/dotenv/src/lib.rs index e39e2de0..f2b1b0ad 100644 --- a/dotenv/src/lib.rs +++ b/dotenv/src/lib.rs @@ -5,20 +5,20 @@ //! file, if available, and mashes those with the actual environment variables //! provided by the operating system. -mod parse; mod errors; -mod iter; mod find; +mod iter; +mod parse; use std::env::{self, Vars}; use std::ffi::OsStr; use std::fs::File; use std::path::{Path, PathBuf}; -use std::sync::{Once}; +use std::sync::Once; pub use crate::errors::*; -use crate::iter::Iter; use crate::find::Finder; +use crate::iter::Iter; static START: Once = Once::new(); @@ -49,7 +49,7 @@ pub fn var>(key: K) -> Result { /// The returned iterator contains a snapshot of the process's environment variables at the /// time of this invocation, modifications to environment variables afterwards will not be /// reflected in the returned iterator. -/// +/// /// Examples: /// /// ```no_run diff --git a/dotenv/src/parse.rs b/dotenv/src/parse.rs index 5fa3deb5..e68f84d3 100644 --- a/dotenv/src/parse.rs +++ b/dotenv/src/parse.rs @@ -5,7 +5,10 @@ use crate::errors::*; // for readability's sake pub type ParsedLine = Result>; -pub fn parse_line(line: &str, substitution_data: &mut HashMap>) -> ParsedLine { +pub fn parse_line( + line: &str, + substitution_data: &mut HashMap>, +) -> ParsedLine { let mut parser = LineParser::new(line, substitution_data); parser.parse_line() } @@ -31,7 +34,7 @@ impl<'a> LineParser<'a> { } fn err(&self) -> Error { - return Error::LineParse(self.original_line.into(), self.pos); + Error::LineParse(self.original_line.into(), self.pos) } fn parse_line(&mut self) -> ParsedLine { @@ -66,7 +69,7 @@ impl<'a> LineParser<'a> { self.substitution_data .insert(key.clone(), Some(parsed_value.clone())); - return Ok(Some((key, parsed_value))); + Ok(Some((key, parsed_value))) } fn parse_key(&mut self) -> Result { @@ -90,7 +93,7 @@ impl<'a> LineParser<'a> { } fn expect_equal(&mut self) -> Result<()> { - if !self.line.starts_with("=") { + if !self.line.starts_with('=') { return Err(self.err()); } self.line = &self.line[1..]; @@ -116,7 +119,10 @@ enum SubstitutionMode { EscapedBlock, } -fn parse_value(input: &str, substitution_data: &mut HashMap>) -> Result { +fn parse_value( + input: &str, + substitution_data: &mut HashMap>, +) -> Result { let mut strong_quote = false; // ' let mut weak_quote = false; // " let mut escaped = false; @@ -148,7 +154,7 @@ fn parse_value(input: &str, substitution_data: &mut HashMap output.push(c), - 'n' => output.push('\n'), // handle \n case + 'n' => output.push('\n'), // handle \n case _ => { return Err(Error::LineParse(input.to_owned(), index)); } @@ -171,7 +177,11 @@ fn parse_value(input: &str, substitution_data: &mut HashMap(), &mut output); + apply_substitution( + substitution_data, + &substitution_name.drain(..).collect::(), + &mut output, + ); if c == '$' { substitution_mode = if !strong_quote && !escaped { SubstitutionMode::Block @@ -187,7 +197,11 @@ fn parse_value(input: &str, substitution_data: &mut HashMap { if c == '}' { substitution_mode = SubstitutionMode::None; - apply_substitution(substitution_data, &substitution_name.drain(..).collect::(), &mut output); + apply_substitution( + substitution_data, + &substitution_name.drain(..).collect::(), + &mut output, + ); } else { substitution_name.push(c); } @@ -224,18 +238,36 @@ fn parse_value(input: &str, substitution_data: &mut HashMap(), &mut output); + 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) { +fn apply_substitution( + substitution_data: &mut HashMap>, + substitution_name: &str, + output: &mut String, +) { if let Ok(environment_value) = std::env::var(substitution_name) { output.push_str(&environment_value); } else { - let stored_value = substitution_data.get(substitution_name).unwrap_or(&None).to_owned(); + let stored_value = substitution_data + .get(substitution_name) + .unwrap_or(&None) + .to_owned(); output.push_str(&stored_value.unwrap_or_else(String::new)); }; } @@ -249,7 +281,8 @@ mod test { #[test] fn test_parse_line_env() { // Note 5 spaces after 'KEY8=' below - let actual_iter = Iter::new(r#" + let actual_iter = Iter::new( + r#" KEY=1 KEY2="2" KEY3='3' @@ -263,7 +296,9 @@ KEY10 ="whitespace before =" KEY11= "whitespace after =" export="export as key" export SHELL_LOVER=1 -"#.as_bytes()); +"# + .as_bytes(), + ); let expected_iter = vec![ ("KEY", "1"), @@ -279,8 +314,9 @@ export SHELL_LOVER=1 ("KEY11", "whitespace after ="), ("export", "export as key"), ("SHELL_LOVER", "1"), - ].into_iter() - .map(|(key, value)| (key.to_string(), value.to_string())); + ] + .into_iter() + .map(|(key, value)| (key.to_string(), value.to_string())); let mut count = 0; for (expected, actual) in expected_iter.zip(actual_iter) { @@ -294,19 +330,26 @@ export SHELL_LOVER=1 #[test] fn test_parse_line_comment() { - let result: Result> = Iter::new(r#" + let result: Result> = Iter::new( + r#" # foo=bar -# "#.as_bytes()).collect(); +# "# + .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#" + let actual_iter = Iter::new( + r#" invalid very bacon = yes indeed -=value"#.as_bytes()); +=value"# + .as_bytes(), + ); let mut count = 0; for actual in actual_iter { @@ -318,7 +361,8 @@ very bacon = yes indeed #[test] fn test_parse_value_escapes() { - let actual_iter = Iter::new(r#" + let actual_iter = Iter::new( + r#" KEY=my\ cool\ value KEY2=\$sweet KEY3="awesome stuff \"mang\"" @@ -326,7 +370,9 @@ KEY4='sweet $\fgs'\''fds' KEY5="'\"yay\\"\ "stuff" KEY6="lol" #well you see when I say lol wh KEY7="line 1\nline 2" -"#.as_bytes()); +"# + .as_bytes(), + ); let expected_iter = vec![ ("KEY", r#"my cool value"#), @@ -336,8 +382,9 @@ KEY7="line 1\nline 2" ("KEY5", r#"'"yay\ stuff"#), ("KEY6", "lol"), ("KEY7", "line 1\nline 2"), - ].into_iter() - .map(|(key, value)| (key.to_string(), value.to_string())); + ] + .into_iter() + .map(|(key, value)| (key.to_string(), value.to_string())); for (expected, actual) in expected_iter.zip(actual_iter) { assert!(actual.is_ok()); @@ -347,12 +394,15 @@ KEY7="line 1\nline 2" #[test] fn test_parse_value_escapes_invalid() { - let actual_iter = Iter::new(r#" + let actual_iter = Iter::new( + r#" KEY=my uncool value KEY2="why KEY3='please stop'' KEY4=h\8u -"#.as_bytes()); +"# + .as_bytes(), + ); for actual in actual_iter { assert!(actual.is_err()); @@ -368,7 +418,8 @@ mod variable_substitution_tests { let actual_iter = Iter::new(input_string.as_bytes()); let expected_count = &expected_parse_result.len(); - let expected_iter = expected_parse_result.into_iter() + let expected_iter = expected_parse_result + .into_iter() .map(|(key, value)| (key.to_string(), value.to_string())); let mut count = 0; @@ -388,30 +439,20 @@ mod variable_substitution_tests { KEY=test KEY1="${KEY}" "#, - vec![ - ("KEY", "test"), - ("KEY1", "test"), - ], + vec![("KEY", "test"), ("KEY1", "test")], ); } #[test] fn substitute_undefined_variables_to_empty_string() { - assert_parsed_string( - r#"KEY=">$KEY1<>${KEY2}<""#, - vec![ - ("KEY", "><><"), - ], - ); + 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}<"), - ], + vec![("KEY", ">$KEY1<>${KEY2}<")], ); } @@ -419,20 +460,13 @@ mod variable_substitution_tests { fn do_not_substitute_variables_in_weak_quotes_with_dollar_escaped() { assert_parsed_string( r#"KEY=">\$KEY1<>\${KEY2}<""#, - vec![ - ("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<"), - ], - ); + assert_parsed_string("KEY='>${KEY1}<>$KEY2<'", vec![("KEY", ">${KEY1}<>$KEY2<")]); } #[test] @@ -442,10 +476,7 @@ mod variable_substitution_tests { KEY=VALUE KEY1=$KEY$KEY "#, - vec![ - ("KEY", "VALUE"), - ("KEY1", "VALUEVALUE"), - ], + vec![("KEY", "VALUE"), ("KEY1", "VALUEVALUE")], ); } @@ -455,13 +486,10 @@ mod variable_substitution_tests { r#" KEY.Value=VALUE "#, - vec![ - ("KEY.Value", "VALUE"), - ], + vec![("KEY.Value", "VALUE")], ); } - #[test] fn recursive_substitution() { assert_parsed_string( @@ -469,10 +497,7 @@ mod variable_substitution_tests { KEY=${KEY1}+KEY_VALUE KEY1=${KEY}+KEY1_VALUE "#, - vec![ - ("KEY", "+KEY_VALUE"), - ("KEY1", "+KEY_VALUE+KEY1_VALUE"), - ], + vec![("KEY", "+KEY_VALUE"), ("KEY1", "+KEY_VALUE+KEY1_VALUE")], ); } @@ -496,12 +521,7 @@ mod variable_substitution_tests { fn substitute_variable_from_env_variable() { std::env::set_var("KEY11", "test_user_env"); - assert_parsed_string( - r#"KEY=">${KEY11}<""#, - vec![ - ("KEY", ">test_user_env<"), - ], - ); + assert_parsed_string(r#"KEY=">${KEY11}<""#, vec![("KEY", ">test_user_env<")]); } #[test] @@ -513,10 +533,7 @@ mod variable_substitution_tests { KEY11=test_user KEY=">${KEY11}<" "#, - vec![ - ("KEY11", "test_user"), - ("KEY", ">test_user_env<"), - ], + vec![("KEY11", "test_user"), ("KEY", ">test_user_env<")], ); } @@ -543,10 +560,7 @@ mod variable_substitution_tests { KEY2=$KEY1_2 KEY=>${KEY1}<>${KEY2}< "#, - vec![ - ("KEY2", "_2"), - ("KEY", "><>_2<"), - ], + vec![("KEY2", "_2"), ("KEY", "><>_2<")], ); } } @@ -560,10 +574,17 @@ mod error_tests { fn should_not_parse_unfinished_substitutions() { let wrong_value = ">${KEY{<"; - let parsed_values: Vec<_> = Iter::new(format!(r#" + let parsed_values: Vec<_> = Iter::new( + format!( + r#" KEY=VALUE KEY1={} - "#, wrong_value).as_bytes()).collect(); + "#, + wrong_value + ) + .as_bytes(), + ) + .collect(); assert_eq!(parsed_values.len(), 2); @@ -615,7 +636,8 @@ mod error_tests { #[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(); + let parsed_values: Vec<_> = + Iter::new(format!("VALUE={}", wrong_escape).as_bytes()).collect(); assert_eq!(parsed_values.len(), 1); diff --git a/dotenv/tests/common/mod.rs b/dotenv/tests/common/mod.rs index 6bdbb8e3..26457b57 100644 --- a/dotenv/tests/common/mod.rs +++ b/dotenv/tests/common/mod.rs @@ -1,6 +1,6 @@ -use std::{env, io}; use 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 { @@ -14,6 +14,5 @@ pub fn tempdir_with_dotenv(dotenv_text: &str) -> io::Result { } pub fn make_test_dotenv() -> io::Result { - tempdir_with_dotenv("TESTKEY=test_val") + tempdir_with_dotenv("TESTKEY=test_val") } - diff --git a/dotenv/tests/test-child-dir.rs b/dotenv/tests/test-child-dir.rs index ca1cf06d..7c2b61e0 100644 --- a/dotenv/tests/test-child-dir.rs +++ b/dotenv/tests/test-child-dir.rs @@ -1,7 +1,7 @@ mod common; -use std::{env, fs}; use dotenv::*; +use std::{env, fs}; use crate::common::*; diff --git a/dotenv/tests/test-default-location.rs b/dotenv/tests/test-default-location.rs index 147b012a..effd973c 100644 --- a/dotenv/tests/test-default-location.rs +++ b/dotenv/tests/test-default-location.rs @@ -1,7 +1,7 @@ mod common; -use std::env; use dotenv::*; +use std::env; use crate::common::*; diff --git a/dotenv/tests/test-dotenv-iter.rs b/dotenv/tests/test-dotenv-iter.rs index 28923039..ec8c1202 100644 --- a/dotenv/tests/test-dotenv-iter.rs +++ b/dotenv/tests/test-dotenv-iter.rs @@ -1,7 +1,7 @@ mod common; -use std::env; use dotenv::*; +use std::env; use crate::common::*; diff --git a/dotenv/tests/test-from-filename-iter.rs b/dotenv/tests/test-from-filename-iter.rs index 97f8c25a..1d3ccbbc 100644 --- a/dotenv/tests/test-from-filename-iter.rs +++ b/dotenv/tests/test-from-filename-iter.rs @@ -1,7 +1,7 @@ mod common; -use std::env; use dotenv::*; +use std::env; use crate::common::*; diff --git a/dotenv/tests/test-from-filename.rs b/dotenv/tests/test-from-filename.rs index bdcf49e4..87dd74b5 100644 --- a/dotenv/tests/test-from-filename.rs +++ b/dotenv/tests/test-from-filename.rs @@ -1,7 +1,7 @@ mod common; -use std::env; use dotenv::*; +use std::env; use crate::common::*; diff --git a/dotenv/tests/test-from-path-iter.rs b/dotenv/tests/test-from-path-iter.rs index bdcb0420..2c4b7636 100644 --- a/dotenv/tests/test-from-path-iter.rs +++ b/dotenv/tests/test-from-path-iter.rs @@ -1,7 +1,7 @@ mod common; -use std::env; use dotenv::*; +use std::env; use crate::common::*; diff --git a/dotenv/tests/test-from-path.rs b/dotenv/tests/test-from-path.rs index e481f021..34aa80a8 100644 --- a/dotenv/tests/test-from-path.rs +++ b/dotenv/tests/test-from-path.rs @@ -1,7 +1,7 @@ mod common; -use std::env; use dotenv::*; +use std::env; use crate::common::*; diff --git a/dotenv/tests/test-variable-substitution.rs b/dotenv/tests/test-variable-substitution.rs index 7860b97a..a6f2e095 100644 --- a/dotenv/tests/test-variable-substitution.rs +++ b/dotenv/tests/test-variable-substitution.rs @@ -1,7 +1,7 @@ mod common; -use std::env; use dotenv::*; +use std::env; use crate::common::*; @@ -11,48 +11,57 @@ fn test_variable_substitutions() { std::env::set_var("KEY1", "value1"); let substitutions_to_test = [ - "$ZZZ", - "$KEY", - "$KEY1", - "${KEY}1", - "$KEY_U", - "${KEY_U}", - "\\$KEY" + "$ZZZ", "$KEY", "$KEY1", "${KEY}1", "$KEY_U", "${KEY_U}", "\\$KEY", ]; let common_string = substitutions_to_test.join(">>"); - let dir = tempdir_with_dotenv(&format!(r#" + let dir = tempdir_with_dotenv(&format!( + r#" KEY1=new_value1 KEY_U=$KEY+valueU SUBSTITUTION_FOR_STRONG_QUOTES='{}' SUBSTITUTION_FOR_WEAK_QUOTES="{}" SUBSTITUTION_WITHOUT_QUOTES={} -"#, common_string, common_string, common_string)).unwrap(); +"#, + common_string, common_string, common_string + )) + .unwrap(); assert_eq!(var("KEY").unwrap(), "value"); assert_eq!(var("KEY1").unwrap(), "value1"); assert_eq!(var("KEY_U").unwrap(), "value+valueU"); - assert_eq!(var("SUBSTITUTION_FOR_STRONG_QUOTES").unwrap(), common_string); - assert_eq!(var("SUBSTITUTION_FOR_WEAK_QUOTES").unwrap(), [ - "", - "value", - "value1", - "value1", - "value_U", - "value+valueU", - "$KEY" - ].join(">>")); - assert_eq!(var("SUBSTITUTION_WITHOUT_QUOTES").unwrap(), [ - "", - "value", - "value1", - "value1", - "value_U", - "value+valueU", - "$KEY" - ].join(">>")); + assert_eq!( + var("SUBSTITUTION_FOR_STRONG_QUOTES").unwrap(), + common_string + ); + assert_eq!( + var("SUBSTITUTION_FOR_WEAK_QUOTES").unwrap(), + [ + "", + "value", + "value1", + "value1", + "value_U", + "value+valueU", + "$KEY" + ] + .join(">>") + ); + assert_eq!( + var("SUBSTITUTION_WITHOUT_QUOTES").unwrap(), + [ + "", + "value", + "value1", + "value1", + "value_U", + "value+valueU", + "$KEY" + ] + .join(">>") + ); env::set_current_dir(dir.path().parent().unwrap()).unwrap(); dir.close().unwrap(); -} \ No newline at end of file +} diff --git a/dotenv/tests/test-vars.rs b/dotenv/tests/test-vars.rs index d7b23a37..a80995c2 100644 --- a/dotenv/tests/test-vars.rs +++ b/dotenv/tests/test-vars.rs @@ -12,7 +12,7 @@ fn test_vars() { let dir = make_test_dotenv().unwrap(); let vars: HashMap = vars().collect(); - + assert_eq!(vars["TESTKEY"], "test_val"); env::set_current_dir(dir.path().parent().unwrap()).unwrap(); diff --git a/dotenv_codegen/src/lib.rs b/dotenv_codegen/src/lib.rs index 00414130..c7588d47 100644 --- a/dotenv_codegen/src/lib.rs +++ b/dotenv_codegen/src/lib.rs @@ -1,4 +1,4 @@ use proc_macro_hack::proc_macro_hack; #[proc_macro_hack] -pub use dotenv_codegen_implementation::dotenv; \ No newline at end of file +pub use dotenv_codegen_implementation::dotenv; diff --git a/dotenv_codegen_implementation/src/lib.rs b/dotenv_codegen_implementation/src/lib.rs index f044b4ae..50a6cc2e 100644 --- a/dotenv_codegen_implementation/src/lib.rs +++ b/dotenv_codegen_implementation/src/lib.rs @@ -1,6 +1,6 @@ extern crate proc_macro; -use std::env; +use std::env::{self, VarError}; use proc_macro::TokenStream; use proc_macro_hack::proc_macro_hack; @@ -20,8 +20,8 @@ pub fn dotenv(input: TokenStream) -> TokenStream { } fn expand_env(input_raw: TokenStream) -> TokenStream { - - let args = >::parse_terminated.parse(input_raw) + let args = >::parse_terminated + .parse(input_raw) .expect("expected macro to be called with a comma-separated list of string literals"); let mut iter = args.iter(); @@ -33,7 +33,7 @@ fn expand_env(input_raw: TokenStream) -> TokenStream { let err_msg = match iter.next() { Some(lit) => lit.value(), - None => format!("environment variable `{}` not defined", var_name).into(), + None => format!("environment variable `{}` not defined", var_name), }; if iter.next().is_some() { @@ -42,6 +42,6 @@ fn expand_env(input_raw: TokenStream) -> TokenStream { match env::var(var_name) { Ok(val) => quote!(#val).into(), - Err(_) => panic!("{}", err_msg), + Err(VarError::NotPresent) | Err(VarError::NotUnicode(_)) => panic!("{}", err_msg), } }