diff --git a/CHANGELOG.md b/CHANGELOG.md index 541b91b..1629f8a 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)) + +### 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)) +- assertions dealing with `Result` (based on [dotenv-rs/dotenv #57](https://github.com/dotenv-rs/dotenv/pull/57)) +- upgraded clap in `dotenvy` bin from v2 to v3.1 (covers [dotenv-rs/dotenv #76](https://github.com/dotenv-rs/dotenv/pull/76)) -## [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 b187085..fcdc3c5 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 3543425..11e157d 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 1f91ab3..ce4badd 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/dotenvy) +![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`](https://crates.io/crates/dotenvy) crate - A well-maintained fork of the `dotenv` crate. +2. [`dotenvy_macro`](https://crates.io/crates/dotenvy_codegen) crate - A macro for compile time dotenv inspection. This is a fork of `dotenv_codegen`. +3. [`dotenvy_codgen_impl`](https://crates.io/crates/dotenvy_codegen_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_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 -```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)) -# 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 -``` +## The legend -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 4c2fb5a..658e7a2 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" -readme = "../README.md" -keywords = ["environment", "env", "dotenv", "settings", "config"] +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/dotenv-rs/dotenv" -edition = "2018" +repository = "https://github.com/allan2/dotenvy" +edition = "2021" [[bin]] -name = "dotenv" +name = "dotenvy" required-features = ["cli"] [dependencies] -clap = { version = "2", optional = true } +clap = { version = "3.1.6", optional = true } +dirs = "4.0" [dev-dependencies] -tempfile = "3.0.0" +tempfile = "3.3.0" [features] cli = ["clap"] diff --git a/dotenv/README.md b/dotenv/README.md new file mode 100644 index 0000000..6fe288f --- /dev/null +++ b/dotenv/README.md @@ -0,0 +1,57 @@ +# dotenvy + +[![crates.io](https://img.shields.io/crates/v/dotenvy.svg)](https://crates.io/crates/dotenvy) +[![Released API docs](https://docs.rs/dotenvy/badge.svg)](https://docs.rs/dotenvy) + +A well-maintained fork of the [dotenv](https://github.com/dotenv-rs/dotenv) crate. + +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_codegen`](https://crates.io/crates/dotenvy_codegen) crate - A macro for compile time dotenv inspection. +3. [`dotenvy_codgen_impl`](https://crates.io/crates/dotenvy_codegen_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 + +### Loading at runtime + +```rs +use dotenvy::dotenv; +use std::env; + +fn main() { + dotenv().ok(); + + for (key, value) in env::vars() { + println!("{key}: {value}"); + } +} +``` + +### Loading at compile time + +The `dotenv!` macro provided by [`dotenvy_codegen`](https://crates.io/crates/dotenvy_codegen) can be used. + +## 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 is intended to serve as the development home for the dotenv implementation in Rust. + +## What are the differences from the original? + +This repo fixes: + +- 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)) + +For a full list of changes, read 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! diff --git a/dotenv/examples/simple.rs b/dotenv/examples/simple.rs deleted file mode 100644 index e2cba32..0000000 --- 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 dce09c6..5f0dfb9 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 a8df844..2561649 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 f2b1b0a..6164488 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 e68f84d..06fa39c 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 26457b5..9b984cc 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 7c2b61e..1db61e8 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 effd973..65e35ff 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 ec8c120..49e5a4c 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 1d3ccbb..eaf4230 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 87dd74b..1be87b0 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 2c4b763..048654e 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 34aa80a..4354fc5 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 7924661..a14a3a4 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 a6f2e09..237825f 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 a80995c..a5a781c 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 5227f0d..6ee214f 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_macro" +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"] +readme = "README.md" +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" -edition = "2018" +homepage = "https://github.com/allan2/dotenvy" +repository = "https://github.com/allan2/dotenvy" +description = "A macro for compile time dotenv inspection" +edition = "2021" [dependencies] -dotenv_codegen_implementation = { version = "0.15", path = "../dotenv_codegen_implementation" } -proc-macro-hack = "0.5" +dotenvy_codegen_impl = { version = "0.15", path = "../dotenv_codegen_impl" } +proc-macro-hack = "0.5.19" diff --git a/dotenv_codegen/README.md b/dotenv_codegen/README.md new file mode 100644 index 0000000..3ca2d53 --- /dev/null +++ b/dotenv_codegen/README.md @@ -0,0 +1,10 @@ +# dotenvy_codgen + +[![crates.io](https://img.shields.io/crates/v/dotenvy_codegen.svg)](https://crates.io/crates/dotenvy_codgen) +[![Released API docs](https://docs.rs/dotenvy_codegen/badge.svg)](https://docs.rs/dotenvy_codegen) + +A macro for compile time dotenv inspection. + +This is a well-maintained fork of `dotenv_codegen`. + +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 diff --git a/dotenv_codegen/src/lib.rs b/dotenv_codegen/src/lib.rs index c7588d4..ae3d20e 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 1b4c1c0..f48c4a7 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_macro::dotenv!("CODEGEN_TEST_VAR1"), "hello!"); } #[test] fn two_argument_form_works() { assert_eq!( - dotenv!( + dotenvy_macro::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_impl/Cargo.toml b/dotenv_codegen_impl/Cargo.toml new file mode 100644 index 0000000..e46f962 --- /dev/null +++ b/dotenv_codegen_impl/Cargo.toml @@ -0,0 +1,29 @@ +[lib] +proc-macro = true + +[package] + +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 = ["dotenv", "env", "environment", "settings", "config"] +license = "MIT" +homepage = "https://github.com/allan2/dotenvy" +repository = "https://github.com/allan2/dotenvy" +description = "Internal implementation for dotenvy_codegen" +edition = "2021" + +[dependencies] +proc-macro2 = "1.0.36" +quote = "1.0.15" +syn = "1.0.86" +proc-macro-hack = "0.5.19" +dotenvy = { version = "0.15", path = "../dotenv" } diff --git a/dotenv_codegen_impl/README.md b/dotenv_codegen_impl/README.md new file mode 100644 index 0000000..5e80d4b --- /dev/null +++ b/dotenv_codegen_impl/README.md @@ -0,0 +1,8 @@ +# dotenvy_codgen_impl + +[![crates.io](https://img.shields.io/crates/v/dotenvy_codegen_impl.svg)](https://crates.io/crates/dotenvy_codgen_impl) +[![Released API docs](https://docs.rs/dotenvy_codegen_impl/badge.svg)](https://docs.rs/dotenvy_codegen_impl) + +Internal implementation for [dotenvy_codgen](https://docs.rs/dotenvy_codegen_impl). + +This is a well-maintained fork of [dotenv_codegen_implementation](https://docs.rs/dotenv_codegen_implementation). diff --git a/dotenv_codegen_impl/src/lib.rs b/dotenv_codegen_impl/src/lib.rs new file mode 100644 index 0000000..befcda8 --- /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/Cargo.toml b/dotenv_codegen_implementation/Cargo.toml deleted file mode 100644 index 23b719b..0000000 --- a/dotenv_codegen_implementation/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[lib] -proc-macro = true - -[package] - -name = "dotenv_codegen_implementation" -version = "0.15.0" -authors = [ - "Santiago Lapresta ", - "Craig Hills ", - "Mike Piccolo ", - "Alice Maz ", - "Sean Griffin ", -] -readme = "../README.md" -keywords = ["environment", "env", "dotenv", "settings", "config"] -license = "MIT" -homepage = "https://github.com/dotenv-rs/dotenv" -repository = "https://github.com/dotenv-rs/dotenv" -description = "A `dotenv` implementation for Rust" -edition = "2018" - -[dependencies] -proc-macro2 = "1" -quote = "1" -syn = "1" -proc-macro-hack = "0.5" -dotenv = { version = "0.15", path = "../dotenv" } diff --git a/dotenv_codegen_implementation/src/lib.rs b/dotenv_codegen_implementation/src/lib.rs deleted file mode 100644 index 50a6cc2..0000000 --- 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), - } -}