dotenv_flow/
lib.rs

1//! This crate provides a configuration loader in the style of the [ruby dotenv
2//! gem](https://github.com/bkeepers/dotenv). This library is meant to be used
3//! on development or testing environments in which setting environment
4//! variables is not practical. It loads environment variables from a .env
5//! file, if available, and mashes those with the actual environment variables
6//! provided by the operating system.
7
8mod errors;
9mod find;
10mod iter;
11mod parse;
12
13use std::env::{self, Vars};
14use std::ffi::OsStr;
15use std::fs::File;
16use std::path::{Path, PathBuf};
17use std::sync::Once;
18
19pub use crate::errors::*;
20use crate::find::Finder;
21use crate::iter::Iter;
22
23static START: Once = Once::new();
24
25/// After loading the dotenv file, fetches the environment variable key from the current process.
26///
27/// The returned result is Ok(s) if the environment variable is present and is valid unicode. If the
28/// environment variable is not present, or it is not valid unicode, then Err will be returned.
29///
30/// Examples:
31///
32/// ```no_run
33///
34/// use dotenv_flow;
35///
36/// let key = "FOO";
37/// let value= dotenv_flow::var(key).unwrap();
38/// ```
39pub fn var<K: AsRef<OsStr>>(key: K) -> Result<String> {
40    START.call_once(|| {
41        dotenv().ok();
42    });
43    env::var(key).map_err(Error::EnvVar)
44}
45
46/// After loading the dotenv file, returns an iterator of (variable, value) pairs of strings,
47/// for all the environment variables of the current process.
48///
49/// The returned iterator contains a snapshot of the process's environment variables at the
50/// time of this invocation, modifications to environment variables afterwards will not be
51/// reflected in the returned iterator.
52///
53/// Examples:
54///
55/// ```no_run
56///
57/// use dotenv_flow;
58/// use std::io;
59///
60/// let result: Vec<(String, String)> = dotenv_flow::vars().collect();
61/// ```
62pub fn vars() -> Vars {
63    START.call_once(|| {
64        dotenv().ok();
65    });
66    env::vars()
67}
68
69/// Loads the file at the specified absolute path.
70///
71/// Examples
72///
73/// ```
74/// use dotenv_flow;
75/// use std::env;
76/// use std::path::{Path};
77///
78/// let my_path = env::home_dir().and_then(|a| Some(a.join("/.env"))).unwrap();
79/// dotenv_flow::from_path(my_path.as_path());
80/// ```
81pub fn from_path<P: AsRef<Path>>(path: P) -> Result<()> {
82    let iter = Iter::new(File::open(path).map_err(Error::Io)?);
83    iter.load()
84}
85
86/// Like `from_path`, but returns an iterator over variables instead of loading into environment.
87///
88/// Examples
89///
90/// ```no_run
91/// use dotenv_flow;
92/// use std::env;
93/// use std::path::{Path};
94///
95/// let my_path = env::home_dir().and_then(|a| Some(a.join("/.env"))).unwrap();
96/// let iter = dotenv_flow::from_path_iter(my_path.as_path()).unwrap();
97///
98/// for item in iter {
99///   let (key, val) = item.unwrap();
100///   println!("{}={}", key, val);
101/// }
102/// ```
103pub fn from_path_iter<P: AsRef<Path>>(path: P) -> Result<Iter<File>> {
104    Ok(Iter::new(File::open(path).map_err(Error::Io)?))
105}
106
107/// Loads the specified file from the environment's current directory or its parents in sequence.
108///
109/// # Examples
110/// ```
111/// use dotenv_flow;
112/// dotenv_flow::from_filename("custom.env").ok();
113/// ```
114///
115/// It is also possible to do the following, but it is equivalent to using `dotenv_flow::dotenv()`,
116/// which is preferred.
117///
118/// ```
119/// use dotenv_flow;
120/// dotenv_flow::from_filename(".env").ok();
121/// ```
122pub fn from_filename<P: AsRef<Path>>(filename: P) -> Result<PathBuf> {
123    let (path, iter) = Finder::new().filename(filename.as_ref()).find()?;
124    iter.load()?;
125    Ok(path)
126}
127
128/// Like `from_filename`, but returns an iterator over variables instead of loading into environment.
129///
130/// # Examples
131/// ```
132/// use dotenv_flow;
133/// dotenv_flow::from_filename("custom.env").ok();
134/// ```
135///
136/// It is also possible to do the following, but it is equivalent to using `dotenv_flow::dotenv()`,
137/// which is preferred.
138///
139/// ```no_run
140/// use dotenv_flow;
141/// let iter = dotenv_flow::from_filename_iter(".env").unwrap();
142///
143/// for item in iter {
144///   let (key, val) = item.unwrap();
145///   println!("{}={}", key, val);
146/// }
147/// ```
148pub fn from_filename_iter<P: AsRef<Path>>(filename: P) -> Result<Iter<File>> {
149    let (_, iter) = Finder::new().filename(filename.as_ref()).find()?;
150    Ok(iter)
151}
152
153/// This is usually what you want.
154/// It loads the .env file located in the environment's current directory or its parents in sequence.
155///
156/// # Examples
157/// ```
158/// use dotenv_flow;
159/// dotenv_flow::dotenv().ok();
160/// ```
161pub fn dotenv() -> Result<PathBuf> {
162    let (path, iter) = Finder::new().find()?;
163    iter.load()?;
164    Ok(path)
165}
166
167/// Similar to dotenv, but implements the dotenv-flow strategy by loading multiple `.env`s following
168/// the order determined by their suffixes.
169///
170/// The strategy is as follows:
171/// - if a DOTENV_ENV environment variable is set, load .env.{DOTENV_ENV}.local (e.g. .env.staging.local)
172/// - load .env.local
173/// - if a DOTENV_ENV environment variable is set, load .env.{DOTENV_ENV} (e.g. .env.staging)
174/// - load .env
175///
176/// # Examples
177/// ```
178/// use dotenv_flow;
179/// dotenv_flow::dotenv_flow().ok();
180/// ```
181pub fn dotenv_flow() -> Result<Vec<PathBuf>> {
182    let preferred_environment = env::var("DOTENV_ENV").ok();
183
184    let candidate_filenames = match preferred_environment {
185        None => vec![PathBuf::from(".env.local"), PathBuf::from(".env")],
186        Some(ref env_name) => vec![
187            PathBuf::from(format!(".env.{}.local", env_name)),
188            PathBuf::from(".env.local"),
189            PathBuf::from(format!(".env.{}", env_name)),
190            PathBuf::from(".env"),
191        ],
192    };
193    let mut path_bufs = vec![];
194    for env_filename in candidate_filenames {
195        match Finder::from_path(&env_filename).find() {
196            Ok((path, iter)) => {
197                iter.load()?;
198                path_bufs.push(path);
199            }
200            _ => (),
201        }
202    }
203
204    Ok(path_bufs)
205}
206
207/// Like `dotenv`, but returns an iterator over variables instead of loading into environment.
208///
209/// # Examples
210/// ```no_run
211/// use dotenv_flow;
212///
213/// for item in dotenv_flow::dotenv_iter().unwrap() {
214///   let (key, val) = item.unwrap();
215///   println!("{}={}", key, val);
216/// }
217/// ```
218pub fn dotenv_iter() -> Result<iter::Iter<File>> {
219    let (_, iter) = Finder::new().find()?;
220    Ok(iter)
221}