Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,33 @@ your crate:
extern crate dotenv_codegen;
```

Then, in your crate:
Then, in your crate you may retrieve a value like so:

```rust
fn main() {
println!("{}", dotenv!("MEANING_OF_LIFE"));
}
```

You may also panic with a supplied error message if the variable does
not exist:

```rust
fn main() {
dotenv!("A_MISSING_VARIABLE", "This is an error message!");
}
```

Or you can supply a default value if the variable does not exist:

```rust
fn main() {
let meaning_of_life: &str = dotenv_or_default!(
"A_MISSING_VARIABLE",
"42"
);
println!("{}", meaning_of_life);
}
```

[dotenv]: https://github.com/bkeepers/dotenv
3 changes: 3 additions & 0 deletions dotenv_codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ edition = "2018"
[dependencies]
dotenv_codegen_implementation = { version = "0.15", path = "../dotenv_codegen_implementation" }
proc-macro-hack = "0.5"

[dev-dependencies]
trybuild = "1.0.32"
2 changes: 1 addition & 1 deletion dotenv_codegen/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use proc_macro_hack::proc_macro_hack;

#[proc_macro_hack]
pub use dotenv_codegen_implementation::dotenv;
pub use dotenv_codegen_implementation::{dotenv, dotenv_or_default};
12 changes: 12 additions & 0 deletions dotenv_codegen/tests/basic_dotenv_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,15 @@ fn two_argument_form_works() {
"'quotes within quotes'"
);
}

#[test]
fn dotenv_or_default_works() {
let default_value: &str = dotenv_or_default!("CODEGEN_TEST_NONEXISTING_VARIABLE", "hello!");
assert_eq!(default_value, "hello!");
}

#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/*.rs");
}
6 changes: 6 additions & 0 deletions dotenv_codegen/tests/ui/dotenv_no_args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#[macro_use]
extern crate dotenv_codegen;

pub fn main() {
dotenv!();
}
16 changes: 16 additions & 0 deletions dotenv_codegen/tests/ui/dotenv_no_args.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
error: proc-macro derive panicked
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this file added intentionally? I can't see a place that it is used.

Copy link
Author

@jharrilim jharrilim Aug 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These stderr files are generated for the tests that use trybuild. It compares this to the stderr from compiling the associated rs file: https://github.com/dtolnay/trybuild#workflow

edit: This style of test is also used in the Rust project itself!
https://github.com/rust-lang/rust/tree/master/src/test/ui

--> $DIR/dotenv_no_args.rs:5:5
|
5 | dotenv!();
| ^^^^^^^^^^
|
= help: message: expected 1 or 2 arguments, found none
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: cannot find macro `proc_macro_call` in this scope
--> $DIR/dotenv_no_args.rs:5:5
|
5 | dotenv!();
| ^^^^^^^^^^
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
6 changes: 6 additions & 0 deletions dotenv_codegen/tests/ui/dotenv_or_default_no_args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#[macro_use]
extern crate dotenv_codegen;

pub fn main() {
dotenv_or_default!();
}
16 changes: 16 additions & 0 deletions dotenv_codegen/tests/ui/dotenv_or_default_no_args.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
error: proc-macro derive panicked
--> $DIR/dotenv_or_default_no_args.rs:5:5
|
5 | dotenv_or_default!();
| ^^^^^^^^^^^^^^^^^^^^^
|
= help: message: expected 1 or 2 arguments, found none
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: cannot find macro `proc_macro_call` in this scope
--> $DIR/dotenv_or_default_no_args.rs:5:5
|
5 | dotenv_or_default!();
| ^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
6 changes: 6 additions & 0 deletions dotenv_codegen/tests/ui/dotenv_or_default_three_args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#[macro_use]
extern crate dotenv_codegen;

pub fn main() {
dotenv_or_default!("a", "b", "c");
}
16 changes: 16 additions & 0 deletions dotenv_codegen/tests/ui/dotenv_or_default_three_args.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
error: proc-macro derive panicked
--> $DIR/dotenv_or_default_three_args.rs:5:5
|
5 | dotenv_or_default!("a", "b", "c");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: message: expected 1 or 2 arguments, found 3 or more
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: cannot find macro `proc_macro_call` in this scope
--> $DIR/dotenv_or_default_three_args.rs:5:5
|
5 | dotenv_or_default!("a", "b", "c");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
6 changes: 6 additions & 0 deletions dotenv_codegen/tests/ui/dotenv_three_args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#[macro_use]
extern crate dotenv_codegen;

pub fn main() {
dotenv!("a", "b", "c");
}
16 changes: 16 additions & 0 deletions dotenv_codegen/tests/ui/dotenv_three_args.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
error: proc-macro derive panicked
--> $DIR/dotenv_three_args.rs:5:5
|
5 | dotenv!("a", "b", "c");
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= help: message: expected 1 or 2 arguments, found 3 or more
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: cannot find macro `proc_macro_call` in this scope
--> $DIR/dotenv_three_args.rs:5:5
|
5 | dotenv!("a", "b", "c");
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
59 changes: 49 additions & 10 deletions dotenv_codegen_implementation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,56 @@ use syn::Token;
#[proc_macro_hack]
pub fn dotenv(input: TokenStream) -> TokenStream {
if let Err(err) = dotenv::dotenv() {
panic!("Error loading .env file: {}", err);
let err_msg = format!("Error loading .env file: {}", err);
return quote! {
compile_error!(#err_msg);
}
.into();
}

// Either everything was fine, or we didn't find an .env file (which we ignore)
expand_env(input)
let (var_name, second_value) = expand_env(input);

let err_msg = match second_value {
Some(e) => e,
None => format!("environment variable `{}` not defined", var_name),
};

match env::var(var_name) {
Ok(val) => quote!(#val).into(),
Err(VarError::NotPresent) | Err(VarError::NotUnicode(_)) => panic!("{}", err_msg),
}
}

#[proc_macro_hack]
pub fn dotenv_or_default(input: TokenStream) -> TokenStream {
if let Err(err) = dotenv::dotenv() {
let err_msg = format!("Error loading .env file: {}", err);
return quote! {
compile_error!(#err_msg);
}
.into();
}

// Either everything was fine, or we didn't find an .env file (which we ignore)
let (var_name, second_value) = expand_env(input);

match second_value {
Some(default) => match env::var(var_name) {
Ok(val) => quote!(#val).into(),
Err(VarError::NotPresent) | Err(VarError::NotUnicode(_)) => quote!(#default).into(),
},
None => {
let err_msg = format!("Missing default value for: {}", var_name);
(quote! {
compile_error!(#err_msg)
})
.into()
}
}
}

fn expand_env(input_raw: TokenStream) -> TokenStream {
fn expand_env(input_raw: TokenStream) -> (String, Option<String>) {
let args = <Punctuated<syn::LitStr, Token![,]>>::parse_terminated
.parse(input_raw)
.expect("expected macro to be called with a comma-separated list of string literals");
Expand All @@ -31,17 +73,14 @@ fn expand_env(input_raw: TokenStream) -> TokenStream {
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),
let second_value = match iter.next() {
Some(lit) => Some(lit.value()),
None => None,
};

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),
}
(var_name, second_value)
}