dotenv_build/lib.rs
1//! # Overview
2//! This crate allows you to load .env files in your compilation step.
3//! It is built to be used in your **[build.rs](https://doc.rust-lang.org/cargo/reference/build-scripts.html)** file.
4//!
5//! # Usage
6//!
7//! 1. Ensure you have build scripts enabled via the `build` configuration in your `Cargo.toml`
8//! 1. Add `dotenv-build` as a build dependency
9//! 1. Create a `build.rs` file that uses `dotenv-build` to generate `cargo:` instructions.
10//! 1. Use the [`env!`](std::env!) or [`option_env!`](std::option_env!) macro in your code
11//!
12//! ### Cargo.toml
13//! ```toml
14//! [package]
15//! #..
16//! build = "build.rs"
17//!
18//! [dependencies]
19//! #..
20//!
21//! [build-dependencies]
22//! dotenv-build = "0.1"
23//! ```
24//!
25//! ### build.rs
26//! ```
27//! // in build.rs
28//! fn main() {
29//! dotenv_build::output(dotenv_build::Config::default()).unwrap();
30//! }
31//! ```
32//!
33//! ### Use in code
34//! ```ignore
35//! println!("Your environment variable in .env: {}", env!("TEST_VARIABLE"));
36//! ```
37//!
38//! [build scripts]: https://doc.rust-lang.org/cargo/reference/build-scripts.html
39//! [cargo:rustc-env]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-env
40//! [cargo:rerun-if-changed]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#rerun-if-changed
41//!
42//! ### Configuration
43//!
44//! Read more about the available options here: [`Config`]
45//! ```
46//! let config = dotenv_build::Config {
47//! filename: std::path::Path::new(".env.other"),
48//! recursive_search: false,
49//! fail_if_missing_dotenv: false,
50//! ..Default::default()
51//! };
52//!
53//! dotenv_build::output(config).unwrap();
54//! ```
55//!
56//! ## Multiple files
57//! Use [`output_multiple`] for this:
58//!
59//! ```
60//! use std::path::Path;
61//!
62//! use dotenv_build::Config;
63//!
64//! let configs = vec![
65//! // load .env.base
66//! Config {
67//! filename: Path::new(".env.base"),
68//! // fail_if_missing_dotenv: true,
69//! ..Default::default()
70//! },
71//! // load .env.staging
72//! Config {
73//! filename: Path::new(".env.staging"),
74//! ..Default::default()
75//! },
76//! // load .env
77//! Config::default(),
78//! ];
79//!
80//! dotenv_build::output_multiple(configs).unwrap();
81//! ```
82
83mod errors;
84mod find;
85mod iter;
86mod parse;
87
88use std::io;
89use std::io::Write;
90use std::path::Path;
91
92use crate::errors::*;
93
94/// Config for [`output`]
95pub struct Config<'a> {
96 /// The filename that is getting read for the environment variables. Defaults to `.env`
97 pub filename: &'a Path,
98 /// This specifies if we should search for the file recursively upwards in the file tree.
99 /// Defaults to `true`.
100 pub recursive_search: bool,
101 /// This specifies if we should return an error if we don't find the file. Defaults to `false`.
102 pub fail_if_missing_dotenv: bool,
103}
104
105impl<'a> Default for Config<'a> {
106 fn default() -> Self {
107 Config {
108 filename: Path::new(".env"),
109 recursive_search: true,
110 fail_if_missing_dotenv: false,
111 }
112 }
113}
114
115/// Outputs the necessary [build.rs](https://doc.rust-lang.org/cargo/reference/build-scripts.html) instructions.
116///
117/// ## Example
118///
119/// ```
120/// dotenv_build::output(dotenv_build::Config::default()).unwrap();
121/// ```
122///
123/// _.env_:
124/// ```text
125/// RUST_LOG=debug
126/// RUST_BACKTRACE=1
127///
128/// ## comment
129/// TEST="hello world!"
130/// ANOTHER_ONE=test
131/// ```
132///
133/// _output_:
134/// ```text
135/// cargo:rustc-env=RUST_LOG=debug
136/// cargo:rustc-env=RUST_BACKTRACE=1
137/// cargo:rustc-env=TEST=hello world!
138/// cargo:rustc-env=ANOTHER_ONE=test
139/// cargo:rerun-if-changed=$PATH_TO_YOUR_FILE/.env
140/// ```
141pub fn output(config: Config) -> Result<()> {
142 output_write_to(config, &mut io::stdout())
143}
144
145/// Same as [`output`] but to read multiple files
146///
147/// ## Example
148///
149/// ```
150/// use std::path::Path;
151///
152/// use dotenv_build::Config;
153///
154/// let configs = vec![
155/// // load .env.base
156/// Config {
157/// filename: Path::new(".env.base"),
158/// // fail_if_missing_dotenv: true,
159/// ..Default::default()
160/// },
161/// // load .env.staging
162/// Config {
163/// filename: Path::new(".env.staging"),
164/// ..Default::default()
165/// },
166/// // load .env
167/// Config::default(),
168/// ];
169///
170/// dotenv_build::output_multiple(configs).unwrap();
171/// ```
172pub fn output_multiple(configs: Vec<Config>) -> Result<()> {
173 for config in configs {
174 output_write_to(config, &mut io::stdout())?;
175 }
176
177 Ok(())
178}
179
180fn output_write_to<T>(config: Config, stdout: &mut T) -> Result<()>
181where
182 T: Write,
183{
184 let (path, lines) = match find::find(&config) {
185 Ok(res) => res,
186 Err(err) if err.not_found() => {
187 return if config.fail_if_missing_dotenv {
188 eprintln!("[dotenv-build] .env file not found, err: {}", err);
189 Err(err)
190 } else {
191 Ok(())
192 };
193 }
194 Err(err) => return Err(err),
195 };
196
197 for line in lines {
198 let (key, value) = match line {
199 Ok(l) => l,
200 Err(err) => {
201 eprintln!("[dotenv-build] {}", err);
202 return Err(err);
203 }
204 };
205
206 writeln!(stdout, "cargo:rustc-env={}={}", key, value)?;
207 }
208
209 writeln!(stdout, "cargo:rerun-if-changed={}", path.to_str().unwrap())?;
210 Ok(())
211}