diff --git a/CHANGELOG.md b/CHANGELOG.md index 541b91b9..fe5203b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,18 +1,45 @@ # Changelog + All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.15.1] - 2022-02-28 + +### Added + +- `dotenv` crate forked as `dotenvy` +- `dotenv_codegen` forked as `dotenvy_codgen` +- `dotenv_codegen_implementation` forked as `dotenvy_codegen_impl` +- Crate description for dotenvy_codegen +- Crate description for dotenvy_codgen_impl +- New language in README +- MIT license badge in README +- Generate helpful errors from dotenv! macro (full merge of [dotenv-rs/dotenv #58](https://github.com/dotenv-rs/dotenv/pull/57/files#)) + +### Changed + +- replaced deprecated `std::env_home:dir()` with `dirs:home_dir` +- Better handling of `home_dir` (merge of [dotenv-rs/dotenv #62](https://github.com/dotenv-rs/dotenv/pull/62/files#)) +- assertions dealing with `Result` (based on [dotenv-rs/dotenv #57](https://github.com/dotenv-rs/dotenv/pull/57/files#)) +- upgraded clap in `dotenvy` bin from v2 to v3.1 (covers [dotenv-rs/dotenv #76](https://github.com/dotenv-rs/dotenv/pull/76/files)) -## [Unreleased] +### Removed + +- example folder. The simple example has been moved to the README. +- `extern` +- unnecessary `use` statements in doc examples ## [0.15.0] - 2019-10-21 ### Changed + - Undeprecate `iter` methods - Library no longer has any dependencies ### Added + - Support for variables with a `.` in their name - Support `\n` in double-quoted lines - Support for variable substitution @@ -20,17 +47,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.14.1] - 2019-05-14 ### Changed + - Deprecate `iter` methods. ## [0.14.0] - 2019-05-07 + ### Changed + - Switched repo to use cargo workspaces. - Renamed dotenv_codegen_impl to dotenv_codegen_implementation since we no longer own the original crate. - Update code to 2018 edition - - -[Unreleased]: https://github.com/dotenv-rs/dotenv/compare/v0.15.0...HEAD +[unreleased]: https://github.com/dotenv-rs/dotenv/compare/v0.15.0...HEAD [0.15.0]: https://github.com/dotenv-rs/dotenv/compare/v0.14.1...v0.15.0 [0.14.1]: https://github.com/dotenv-rs/dotenv/compare/v0.14.0...v0.14.1 -[0.14.0]: https://github.com/dotenv-rs/dotenv/compare/v0.13.0...v0.14.0 \ No newline at end of file +[0.14.0]: https://github.com/dotenv-rs/dotenv/compare/v0.13.0...v0.14.0 diff --git a/Cargo.toml b/Cargo.toml index b187085e..fcdc3c54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,3 @@ [workspace] -members = [ - "dotenv", - "dotenv_codegen", - "dotenv_codegen_implementation", -] \ No newline at end of file +members = ["dotenv", "dotenv_codegen", "dotenv_codegen_impl"] diff --git a/LICENSE.md b/LICENSE similarity index 98% rename from LICENSE.md rename to LICENSE index 3543425b..11e157d4 100644 --- a/LICENSE.md +++ b/LICENSE @@ -18,4 +18,4 @@ 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. +THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 1f91ab39..77594a04 100644 --- a/README.md +++ b/README.md @@ -1,129 +1,61 @@ -# rust-dotenv +# dotenvy -![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) +[![crates.io](https://img.shields.io/crates/v/dotenvy.svg)](https://crates.io/crates/dotenv) +![CI](https://github.com/allan2/dotenvy/workflows/CI/badge.svg) +[![Released API docs](https://docs.rs/dotenvy/badge.svg)](https://docs.rs/dotenvy) +[![codecov](https://codecov.io/gh/allan2/dotenvy/branch/master/graph/badge.svg)](https://codecov.io/gh/allan2/dotenvy) -**Achtung!** This is a v0.\* version! Expect bugs and issues all around. -Submitting pull requests and issues is highly encouraged! +A well-maintained fork of the [dotenv](https://github.com/dotenv-rs/dotenv) crate. -Quoting [bkeepers/dotenv][dotenv]: +This library loads environment variables from a _.env_ file. This is convenient for dev environments. -> Storing [configuration in the environment](http://www.12factor.net/config) -> is one of the tenets of a [twelve-factor app](http://www.12factor.net/). -> Anything that is likely to change between deployment environments–such as -> resource handles for databases or credentials for external services–should -> be extracted from the code into environment variables. +## Components -This library is meant to be used on development or testing environments in -which setting environment variables is not practical. It loads environment -variables from a `.env` file, if available, and mashes those with the actual -environment variables provided by the operative system. +1. `dotenvy` crate - A well-maintained fork of the `dotenv` crate. +2. `dotenvy_codegen` crate - A macro for compile time dotenv inspection. +3. `dotenvy_codgen_impl` crate - Internal implementation for dotenvy_codegen. +4. `dotenvy` CLI tool for running a command using the environment from a _.env_ file (currently Unix only) -Usage ----- +## Usage -The easiest and most common usage consists on calling `dotenv::dotenv` when the -application starts, which will load environment variables from a file named -`.env` in the current directory or any of its parents; after that, you can just call -the environment-related method you need as provided by `std::os`. +### Loading at runtime -If you need finer control about the name of the file or its location, you can -use the `from_filename` and `from_path` methods provided by the crate. - -`dotenv_codegen` provides the `dotenv!` macro, which -behaves identically to `env!`, but first tries to load a `.env` file at compile -time. - -Examples ----- - -A `.env` file looks like this: - -```sh -# a comment, will be ignored -REDIS_ADDRESS=localhost:6379 -MEANING_OF_LIFE=42 -``` - -You can optionally prefix each line with the word `export`, which will -conveniently allow you to source the whole file on your shell. - -A sample project using Dotenv would look like this: - -```rust -extern crate dotenv; - -use dotenv::dotenv; +```rs +use dotenvy::dotenv; use std::env; fn main() { dotenv().ok(); for (key, value) in env::vars() { - println!("{}: {}", key, value); + println!("{key}: {value}"); } } ``` -Variable substitution ----- +### Loading at compile time -It's possible to reuse variables in the `.env` file using `$VARIABLE` syntax. -The syntax and rules are similar to bash ones, here's the example: +The `dotenv!` macro provided by `dotenvy_codegen` 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 -```sh +## Why does this fork exist? -VAR=one -VAR_2=two +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)). -# Non-existing values are replaced with an empty string -RESULT=$NOPE #value: '' (empty string) +This fork is intended to serve as the development home for the dotenv implementation in Rust. -# All the letters after $ symbol are treated as the variable name to replace -RESULT=$VAR #value: 'one' +## What are the differences from the original? -# Double quotes do not affect the substitution -RESULT="$VAR" #value: 'one' +This repo fixes: -# Different syntax, same result -RESULT=${VAR} #value: 'one' +- home directory works correctly (no longer using the deprecated `std::env::home_dir`) +- more helpful errors for `dotenv!` ([dotenv-rs/dotenv #57](https://github.com/dotenv-rs/dotenv/pull/57/files#)) -# Curly braces are useful in cases when we need to use a variable with non-alphanumeric name -RESULT=$VAR_2 #value: 'one_2' since $ with no curly braces stops after first non-alphanumeric symbol -RESULT=${VAR_2} #value: 'two' +For a full list of changes, read the [changelog](./CHANGELOG.md). -# The replacement can be escaped with either single quotes or a backslash: -RESULT='$VAR' #value: '$VAR' -RESULT=\$VAR #value: '$VAR' - -# Environment variables are used in the substutution and always override the local variables -RESULT=$PATH #value: the contents of the $PATH environment variable -PATH="My local variable value" -RESULT=$PATH #value: the contents of the $PATH environment variable, even though the local variable is defined -``` +## Are you a usurper of the dotenv legacy? -Dotenv will parse the file, substituting the variables the way it's described in the comments. - - -Using the `dotenv!` macro ------------------------------------- - -Add `dotenv_codegen` to your dependencies, and add the following to the top of -your crate: - -```rust -#[macro_use] -extern crate dotenv_codegen; -``` - -Then, in your crate: - -```rust -fn main() { - println!("{}", dotenv!("MEANING_OF_LIFE")); -} -``` +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. -[dotenv]: https://github.com/bkeepers/dotenv +Until then, this repo dutifully carries on the dotenv torch. It is actively maintained. Contributions and PRs are very welcome! diff --git a/dotenv/Cargo.toml b/dotenv/Cargo.toml index 4c2fb5ac..ca1d5c58 100644 --- a/dotenv/Cargo.toml +++ b/dotenv/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "dotenv" -version = "0.15.0" +name = "dotenvy" +version = "0.15.1" authors = [ "Noemi Lapresta ", "Craig Hills ", @@ -9,24 +9,26 @@ authors = [ "Sean Griffin ", "Adam Sharp ", "Arpad Borsos ", + "Allan Zhang ", ] -description = "A `dotenv` implementation for Rust" -homepage = "https://github.com/dotenv-rs/dotenv" +description = "A well-maintained fork of the `dotenv` crate" +homepage = "https://github.com/allan2/dotenvy" readme = "../README.md" -keywords = ["environment", "env", "dotenv", "settings", "config"] +keywords = ["dotenv", "env", "environment", "settings", "config"] license = "MIT" -repository = "https://github.com/dotenv-rs/dotenv" +repository = "https://github.com/allan2/dotenvy" edition = "2018" [[bin]] -name = "dotenv" +name = "dotenvy" required-features = ["cli"] [dependencies] -clap = { version = "2", optional = true } +clap = { version = "3.1", optional = true } +dirs = "4.0" [dev-dependencies] -tempfile = "3.0.0" +tempfile = "3.3.0" [features] cli = ["clap"] diff --git a/dotenv/examples/simple.rs b/dotenv/examples/simple.rs deleted file mode 100644 index e2cba321..00000000 --- a/dotenv/examples/simple.rs +++ /dev/null @@ -1,10 +0,0 @@ -use dotenv::dotenv; -use std::env; - -fn main() { - dotenv().ok(); - - for (key, value) in env::vars() { - println!("{}: {}", key, value); - } -} diff --git a/dotenv/src/bin/dotenv.rs b/dotenv/src/bin/dotenvy.rs similarity index 59% rename from dotenv/src/bin/dotenv.rs rename to dotenv/src/bin/dotenvy.rs index dce09c6d..5f0dfb95 100644 --- a/dotenv/src/bin/dotenv.rs +++ b/dotenv/src/bin/dotenvy.rs @@ -1,23 +1,20 @@ -extern crate clap; -extern crate dotenv; - -use clap::{App, AppSettings, Arg}; +use clap::Arg; use std::os::unix::process::CommandExt; -use std::process::{exit, Command}; +use std::process; macro_rules! die { ($fmt:expr) => ({ eprintln!($fmt); - exit(1); + process::exit(1); }); ($fmt:expr, $($arg:tt)*) => ({ eprintln!($fmt, $($arg)*); - exit(1); + process::exit(1); }); } -fn make_command(name: &str, args: Vec<&str>) -> Command { - let mut command = Command::new(name); +fn make_command(name: &str, args: Vec<&str>) -> process::Command { + let mut command = process::Command::new(name); for arg in args { command.arg(arg); @@ -27,15 +24,14 @@ fn make_command(name: &str, args: Vec<&str>) -> Command { } fn main() { - let matches = App::new("dotenv") + let matches = clap::Command::new("dotenvy") .about("Run a command using the environment in a .env file") - .usage("dotenv [ARGS]...") - .setting(AppSettings::AllowExternalSubcommands) - .setting(AppSettings::ArgRequiredElseHelp) - .setting(AppSettings::UnifiedHelpMessage) + .override_usage("dotenvy [ARGS]...") + .allow_external_subcommands(true) + .arg_required_else_help(true) .arg( - Arg::with_name("FILE") - .short("f") + Arg::new("FILE") + .short('f') .long("file") .takes_value(true) .help("Use a specific .env file (defaults to .env)"), @@ -43,13 +39,13 @@ fn main() { .get_matches(); match matches.value_of("FILE") { - None => dotenv::dotenv(), - Some(file) => dotenv::from_filename(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() { - (name, Some(matches)) => { + Some((name, matches)) => { let args = matches .values_of("") .map(|v| v.collect()) @@ -57,12 +53,12 @@ fn main() { make_command(name, args) } - _ => die!("error: missing required argument "), + None => die!("error: missing required argument "), }; if cfg!(target_os = "windows") { match command.spawn().and_then(|mut child| child.wait()) { - Ok(status) => exit(status.code().unwrap_or(1)), + Ok(status) => process::exit(status.code().unwrap_or(1)), Err(error) => die!("fatal: {}", error), }; } else { diff --git a/dotenv/src/errors.rs b/dotenv/src/errors.rs index a8df8449..25616494 100644 --- a/dotenv/src/errors.rs +++ b/dotenv/src/errors.rs @@ -1,3 +1,4 @@ +use std::env; use std::error; use std::fmt; use std::io; @@ -8,7 +9,7 @@ pub type Result = std::result::Result; pub enum Error { LineParse(String, usize), Io(io::Error), - EnvVar(std::env::VarError), + EnvVar(env::VarError), #[doc(hidden)] __Nonexhaustive, } @@ -49,30 +50,28 @@ impl fmt::Display for Error { #[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(std::io::ErrorKind::PermissionDenied.into()); - let io_err = err - .source() - .unwrap() - .downcast_ref::() - .unwrap(); - assert_eq!(std::io::ErrorKind::PermissionDenied, io_err.kind()); + 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(std::env::VarError::NotPresent); + let err = Error::EnvVar(env::VarError::NotPresent); let var_err = err .source() .unwrap() - .downcast_ref::() + .downcast_ref::() .unwrap(); - assert_eq!(&std::env::VarError::NotPresent, var_err); + assert_eq!(&env::VarError::NotPresent, var_err); } #[test] @@ -83,20 +82,20 @@ mod test { #[test] fn test_error_not_found_true() { - let err = Error::Io(std::io::ErrorKind::NotFound.into()); + let err = Error::Io(io::ErrorKind::NotFound.into()); assert!(err.not_found()); } #[test] fn test_error_not_found_false() { - let err = Error::Io(std::io::ErrorKind::PermissionDenied.into()); + let err = Error::Io(io::ErrorKind::PermissionDenied.into()); assert!(!err.not_found()); } #[test] fn test_io_error_display() { - let err = Error::Io(std::io::ErrorKind::PermissionDenied.into()); - let io_err: std::io::Error = std::io::ErrorKind::PermissionDenied.into(); + 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); @@ -105,8 +104,8 @@ mod test { #[test] fn test_envvar_error_display() { - let err = Error::EnvVar(std::env::VarError::NotPresent); - let var_err = std::env::VarError::NotPresent; + 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); diff --git a/dotenv/src/lib.rs b/dotenv/src/lib.rs index f2b1b0ad..6164488e 100644 --- a/dotenv/src/lib.rs +++ b/dotenv/src/lib.rs @@ -4,6 +4,7 @@ //! variables is not practical. It loads environment variables from a .env //! file, if available, and mashes those with the actual environment variables //! provided by the operating system. +#![forbid(unsafe_code)] mod errors; mod find; @@ -30,11 +31,8 @@ static START: Once = Once::new(); /// Examples: /// /// ```no_run -/// -/// use dotenv; -/// /// let key = "FOO"; -/// let value= dotenv::var(key).unwrap(); +/// let value= dotenvy::var(key).unwrap(); /// ``` pub fn var>(key: K) -> Result { START.call_once(|| { @@ -53,11 +51,9 @@ pub fn var>(key: K) -> Result { /// Examples: /// /// ```no_run -/// -/// use dotenv; /// use std::io; /// -/// let result: Vec<(String, String)> = dotenv::vars().collect(); +/// let result: Vec<(String, String)> = dotenvy::vars().collect(); /// ``` pub fn vars() -> Vars { START.call_once(|| { @@ -71,12 +67,11 @@ pub fn vars() -> Vars { /// Examples /// /// ``` -/// use dotenv; /// use std::env; /// use std::path::{Path}; /// -/// let my_path = env::home_dir().and_then(|a| Some(a.join("/.env"))).unwrap(); -/// dotenv::from_path(my_path.as_path()); +/// let my_path = dirs::home_dir().map(|a| a.join("/.env")).unwrap(); +/// dotenvy::from_path(my_path.as_path()); /// ``` pub fn from_path>(path: P) -> Result<()> { let iter = Iter::new(File::open(path).map_err(Error::Io)?); @@ -88,12 +83,11 @@ pub fn from_path>(path: P) -> Result<()> { /// Examples /// /// ```no_run -/// use dotenv; /// use std::env; /// use std::path::{Path}; /// -/// let my_path = env::home_dir().and_then(|a| Some(a.join("/.env"))).unwrap(); -/// let iter = dotenv::from_path_iter(my_path.as_path()).unwrap(); +/// let my_path = dirs::home_dir().map(|a| a.join("/.env")).unwrap(); +/// let iter = dotenvy::from_path_iter(my_path.as_path()).unwrap(); /// /// for item in iter { /// let (key, val) = item.unwrap(); @@ -108,16 +102,14 @@ pub fn from_path_iter>(path: P) -> Result> { /// /// # Examples /// ``` -/// use dotenv; -/// dotenv::from_filename("custom.env").ok(); +/// dotenvy::from_filename("custom.env").ok(); /// ``` /// -/// It is also possible to do the following, but it is equivalent to using `dotenv::dotenv()`, +/// It is also possible to do the following, but it is equivalent to using `dotenvy::dotenv()`, /// which is preferred. /// /// ``` -/// use dotenv; -/// dotenv::from_filename(".env").ok(); +/// dotenvy::from_filename(".env").ok(); /// ``` pub fn from_filename>(filename: P) -> Result { let (path, iter) = Finder::new().filename(filename.as_ref()).find()?; @@ -129,16 +121,14 @@ pub fn from_filename>(filename: P) -> Result { /// /// # Examples /// ``` -/// use dotenv; -/// dotenv::from_filename("custom.env").ok(); +/// dotenvy::from_filename("custom.env").ok(); /// ``` /// -/// It is also possible to do the following, but it is equivalent to using `dotenv::dotenv()`, +/// It is also possible to do the following, but it is equivalent to using `dotenvy::dotenv()`, /// which is preferred. /// /// ```no_run -/// use dotenv; -/// let iter = dotenv::from_filename_iter(".env").unwrap(); +/// let iter = dotenvy::from_filename_iter(".env").unwrap(); /// /// for item in iter { /// let (key, val) = item.unwrap(); @@ -155,8 +145,7 @@ pub fn from_filename_iter>(filename: P) -> Result> { /// /// # Examples /// ``` -/// use dotenv; -/// dotenv::dotenv().ok(); +/// dotenvy::dotenv().ok(); /// ``` pub fn dotenv() -> Result { let (path, iter) = Finder::new().find()?; @@ -168,9 +157,7 @@ pub fn dotenv() -> Result { /// /// # Examples /// ```no_run -/// use dotenv; -/// -/// for item in dotenv::dotenv_iter().unwrap() { +/// for item in dotenvy::dotenv_iter().unwrap() { /// let (key, val) = item.unwrap(); /// println!("{}={}", key, val); /// } diff --git a/dotenv/src/parse.rs b/dotenv/src/parse.rs index e68f84d3..06fa39c7 100644 --- a/dotenv/src/parse.rs +++ b/dotenv/src/parse.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::env; use crate::errors::*; @@ -261,7 +262,7 @@ fn apply_substitution( substitution_name: &str, output: &mut String, ) { - if let Ok(environment_value) = std::env::var(substitution_name) { + if let Ok(environment_value) = env::var(substitution_name) { output.push_str(&environment_value); } else { let stored_value = substitution_data @@ -321,7 +322,7 @@ export SHELL_LOVER=1 let mut count = 0; for (expected, actual) in expected_iter.zip(actual_iter) { assert!(actual.is_ok()); - assert_eq!(expected, actual.ok().unwrap()); + assert_eq!(expected, actual.unwrap()); count += 1; } @@ -413,6 +414,7 @@ KEY4=h\8u #[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()); @@ -425,7 +427,7 @@ mod variable_substitution_tests { let mut count = 0; for (expected, actual) in expected_iter.zip(actual_iter) { assert!(actual.is_ok()); - assert_eq!(expected, actual.ok().unwrap()); + assert_eq!(expected, actual.unwrap()); count += 1; } @@ -519,14 +521,14 @@ mod variable_substitution_tests { #[test] fn substitute_variable_from_env_variable() { - std::env::set_var("KEY11", "test_user_env"); + 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() { - std::env::set_var("KEY11", "test_user_env"); + env::set_var("KEY11", "test_user_env"); assert_parsed_string( r#" @@ -591,14 +593,14 @@ mod error_tests { if let Ok(first_line) = &parsed_values[0] { assert_eq!(first_line, &(String::from("KEY"), String::from("VALUE"))) } else { - assert!(false, "Expected the first value to be parsed") + 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 { - assert!(false, "Expected the second value not to be parsed") + panic!("Expected the second value not to be parsed") } } @@ -614,7 +616,7 @@ mod error_tests { assert_eq!(second_value, wrong_key_value); assert_eq!(*index, 0) } else { - assert!(false, "Expected the second value not to be parsed") + panic!("Expected the second value not to be parsed") } } @@ -629,7 +631,7 @@ mod error_tests { assert_eq!(wrong_value, wrong_format); assert_eq!(*index, 0) } else { - assert!(false, "Expected the second value not to be parsed") + panic!("Expected the second value not to be parsed") } } @@ -645,7 +647,7 @@ mod error_tests { assert_eq!(wrong_value, wrong_escape); assert_eq!(*index, wrong_escape.find("\\").unwrap() + 1) } else { - assert!(false, "Expected the second value not to be parsed") + panic!("Expected the second value not to be parsed") } } } diff --git a/dotenv/tests/common/mod.rs b/dotenv/tests/common/mod.rs index 26457b57..9b984cc1 100644 --- a/dotenv/tests/common/mod.rs +++ b/dotenv/tests/common/mod.rs @@ -13,6 +13,7 @@ pub fn tempdir_with_dotenv(dotenv_text: &str) -> io::Result { Ok(dir) } +#[allow(dead_code)] pub fn make_test_dotenv() -> io::Result { tempdir_with_dotenv("TESTKEY=test_val") } diff --git a/dotenv/tests/test-child-dir.rs b/dotenv/tests/test-child-dir.rs index 7c2b61e0..1db61e8d 100644 --- a/dotenv/tests/test-child-dir.rs +++ b/dotenv/tests/test-child-dir.rs @@ -1,6 +1,6 @@ mod common; -use dotenv::*; +use dotenvy::*; use std::{env, fs}; use crate::common::*; diff --git a/dotenv/tests/test-default-location.rs b/dotenv/tests/test-default-location.rs index effd973c..65e35ffa 100644 --- a/dotenv/tests/test-default-location.rs +++ b/dotenv/tests/test-default-location.rs @@ -1,6 +1,6 @@ mod common; -use dotenv::*; +use dotenvy::*; use std::env; use crate::common::*; diff --git a/dotenv/tests/test-dotenv-iter.rs b/dotenv/tests/test-dotenv-iter.rs index ec8c1202..49e5a4c1 100644 --- a/dotenv/tests/test-dotenv-iter.rs +++ b/dotenv/tests/test-dotenv-iter.rs @@ -1,6 +1,6 @@ mod common; -use dotenv::*; +use dotenvy::*; 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 1d3ccbbc..eaf42300 100644 --- a/dotenv/tests/test-from-filename-iter.rs +++ b/dotenv/tests/test-from-filename-iter.rs @@ -1,6 +1,6 @@ mod common; -use dotenv::*; +use dotenvy::*; use std::env; use crate::common::*; diff --git a/dotenv/tests/test-from-filename.rs b/dotenv/tests/test-from-filename.rs index 87dd74b5..1be87b00 100644 --- a/dotenv/tests/test-from-filename.rs +++ b/dotenv/tests/test-from-filename.rs @@ -1,6 +1,6 @@ mod common; -use dotenv::*; +use dotenvy::*; 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 2c4b7636..048654ec 100644 --- a/dotenv/tests/test-from-path-iter.rs +++ b/dotenv/tests/test-from-path-iter.rs @@ -1,6 +1,6 @@ mod common; -use dotenv::*; +use dotenvy::*; use std::env; use crate::common::*; diff --git a/dotenv/tests/test-from-path.rs b/dotenv/tests/test-from-path.rs index 34aa80a8..4354fc53 100644 --- a/dotenv/tests/test-from-path.rs +++ b/dotenv/tests/test-from-path.rs @@ -1,6 +1,6 @@ mod common; -use dotenv::*; +use dotenvy::*; use std::env; use crate::common::*; diff --git a/dotenv/tests/test-var.rs b/dotenv/tests/test-var.rs index 79246617..a14a3a44 100644 --- a/dotenv/tests/test-var.rs +++ b/dotenv/tests/test-var.rs @@ -2,7 +2,7 @@ mod common; use std::env; -use dotenv::*; +use dotenvy::*; use crate::common::*; diff --git a/dotenv/tests/test-variable-substitution.rs b/dotenv/tests/test-variable-substitution.rs index a6f2e095..237825f2 100644 --- a/dotenv/tests/test-variable-substitution.rs +++ b/dotenv/tests/test-variable-substitution.rs @@ -1,6 +1,6 @@ mod common; -use dotenv::*; +use dotenvy::*; use std::env; use crate::common::*; diff --git a/dotenv/tests/test-vars.rs b/dotenv/tests/test-vars.rs index a80995c2..a5a781c9 100644 --- a/dotenv/tests/test-vars.rs +++ b/dotenv/tests/test-vars.rs @@ -3,7 +3,7 @@ mod common; use std::collections::HashMap; use std::env; -use dotenv::*; +use dotenvy::*; use crate::common::*; diff --git a/dotenv_codegen/Cargo.toml b/dotenv_codegen/Cargo.toml index 5227f0dc..7946556b 100644 --- a/dotenv_codegen/Cargo.toml +++ b/dotenv_codegen/Cargo.toml @@ -1,22 +1,23 @@ [package] -name = "dotenv_codegen" -version = "0.15.0" +name = "dotenvy-codegen" +version = "0.15.1" authors = [ "Santiago Lapresta ", "Craig Hills ", "Mike Piccolo ", "Alice Maz ", "Sean Griffin ", + "Allan Zhang ", ] readme = "../README.md" -keywords = ["environment", "env", "dotenv", "settings", "config"] +keywords = ["dotenv", "env", "environment", "settings", "config"] license = "MIT" -homepage = "https://github.com/dotenv-rs/dotenv" -repository = "https://github.com/dotenv-rs/dotenv" -description = "A `dotenv` implementation for Rust" +homepage = "https://github.com/allan2/dotenvy" +repository = "https://github.com/allan2/dotenvy" +description = "A macro for compile time dotenv inspection" edition = "2018" [dependencies] -dotenv_codegen_implementation = { version = "0.15", path = "../dotenv_codegen_implementation" } +dotenvy_codegen_impl = { version = "0.15", path = "../dotenv_codegen_impl" } proc-macro-hack = "0.5" diff --git a/dotenv_codegen/src/lib.rs b/dotenv_codegen/src/lib.rs index c7588d47..ae3d20e0 100644 --- a/dotenv_codegen/src/lib.rs +++ b/dotenv_codegen/src/lib.rs @@ -1,4 +1,6 @@ +#![forbid(unsafe_code)] + use proc_macro_hack::proc_macro_hack; #[proc_macro_hack] -pub use dotenv_codegen_implementation::dotenv; +pub use dotenvy_codegen_impl::dotenv; diff --git a/dotenv_codegen/tests/basic_dotenv_macro.rs b/dotenv_codegen/tests/basic_dotenv_macro.rs index 1b4c1c0d..d86b2bde 100644 --- a/dotenv_codegen/tests/basic_dotenv_macro.rs +++ b/dotenv_codegen/tests/basic_dotenv_macro.rs @@ -1,15 +1,12 @@ -#[macro_use] -extern crate dotenv_codegen; - #[test] fn dotenv_works() { - assert_eq!(dotenv!("CODEGEN_TEST_VAR1"), "hello!"); + assert_eq!(dotenvy_codegen::dotenv!("CODEGEN_TEST_VAR1"), "hello!"); } #[test] fn two_argument_form_works() { assert_eq!( - dotenv!( + dotenvy_codegen::dotenv!( "CODEGEN_TEST_VAR2", "err, you should be running this in the 'dotenv_codegen' \ directory to pick up the right .env file." diff --git a/dotenv_codegen_implementation/Cargo.toml b/dotenv_codegen_impl/Cargo.toml similarity index 53% rename from dotenv_codegen_implementation/Cargo.toml rename to dotenv_codegen_impl/Cargo.toml index 23b719b5..a36f19f9 100644 --- a/dotenv_codegen_implementation/Cargo.toml +++ b/dotenv_codegen_impl/Cargo.toml @@ -3,21 +3,22 @@ proc-macro = true [package] -name = "dotenv_codegen_implementation" -version = "0.15.0" +name = "dotenvy_codegen_impl" +version = "0.15.1" authors = [ "Santiago Lapresta ", "Craig Hills ", "Mike Piccolo ", "Alice Maz ", "Sean Griffin ", + "Allan Zhang ", ] readme = "../README.md" -keywords = ["environment", "env", "dotenv", "settings", "config"] +keywords = ["dotenv", "env", "environment", "settings", "config"] license = "MIT" -homepage = "https://github.com/dotenv-rs/dotenv" -repository = "https://github.com/dotenv-rs/dotenv" -description = "A `dotenv` implementation for Rust" +homepage = "https://github.com/allan2/dotenvy" +repository = "https://github.com/allan2/dotenvy" +description = "Internal implementation for dotenvy_codegen" edition = "2018" [dependencies] @@ -25,4 +26,4 @@ proc-macro2 = "1" quote = "1" syn = "1" proc-macro-hack = "0.5" -dotenv = { version = "0.15", path = "../dotenv" } +dotenvy = { version = "0.15", path = "../dotenv" } diff --git a/dotenv_codegen_impl/src/lib.rs b/dotenv_codegen_impl/src/lib.rs new file mode 100644 index 00000000..befcda85 --- /dev/null +++ b/dotenv_codegen_impl/src/lib.rs @@ -0,0 +1,67 @@ +#![forbid(unsafe_code)] + +use std::env::{self, VarError}; + +use proc_macro::TokenStream; +use proc_macro_hack::proc_macro_hack; +use quote::quote; +use syn::parse::Parser; +use syn::punctuated::Punctuated; +use syn::spanned::Spanned; +use syn::Token; + +#[proc_macro_hack] +pub fn dotenv(input: TokenStream) -> TokenStream { + if let Err(err) = dotenvy::dotenv() { + let msg = format!("Error loading .env file: {}", err); + return quote! { + compile_error!(#msg); + } + .into(); + } + + match expand_env(input) { + Ok(stream) => stream, + Err(e) => e.to_compile_error().into(), + } +} + +fn expand_env(input_raw: TokenStream) -> syn::Result { + 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(); + + let var_name = iter + .next() + .ok_or_else(|| syn::Error::new(args.span(), "dotenv! takes 1 or 2 arguments"))? + .value(); + let err_msg = iter.next(); + + if iter.next().is_some() { + return Err(syn::Error::new( + args.span(), + "dotenv! takes 1 or 2 arguments", + )); + } + + match env::var(&var_name) { + Ok(val) => Ok(quote!(#val).into()), + Err(e) => Err(syn::Error::new( + var_name.span(), + err_msg.map_or_else( + || match e { + VarError::NotPresent => { + format!("environment variable `{}` not defined", var_name) + } + VarError::NotUnicode(s) => format!( + "environment variable `{}` was not valid unicode: {:?}", + var_name, s + ), + }, + |lit| lit.value(), + ), + )), + } +} diff --git a/dotenv_codegen_implementation/src/lib.rs b/dotenv_codegen_implementation/src/lib.rs deleted file mode 100644 index 50a6cc2e..00000000 --- a/dotenv_codegen_implementation/src/lib.rs +++ /dev/null @@ -1,47 +0,0 @@ -extern crate proc_macro; - -use std::env::{self, VarError}; - -use proc_macro::TokenStream; -use proc_macro_hack::proc_macro_hack; -use quote::quote; -use syn::parse::Parser; -use syn::punctuated::Punctuated; -use syn::Token; - -#[proc_macro_hack] -pub fn dotenv(input: TokenStream) -> TokenStream { - if let Err(err) = dotenv::dotenv() { - panic!("Error loading .env file: {}", err); - } - - // Either everything was fine, or we didn't find an .env file (which we ignore) - expand_env(input) -} - -fn expand_env(input_raw: TokenStream) -> TokenStream { - 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(); - - let var_name = match iter.next() { - Some(s) => s.value(), - None => panic!("expected 1 or 2 arguments, found none"), - }; - - let err_msg = match iter.next() { - Some(lit) => lit.value(), - None => format!("environment variable `{}` not defined", var_name), - }; - - if iter.next().is_some() { - panic!("expected 1 or 2 arguments, found 3 or more"); - } - - match env::var(var_name) { - Ok(val) => quote!(#val).into(), - Err(VarError::NotPresent) | Err(VarError::NotUnicode(_)) => panic!("{}", err_msg), - } -}