env_file_reader/lib.rs
1#![doc = include_str!("../README.md")]
2
3use lalrpop_util::lalrpop_mod;
4
5use std::collections::HashMap;
6use std::fs;
7use std::io::{Error, ErrorKind};
8use std::path::Path;
9
10lalrpop_mod!(env_file);
11
12mod lexer;
13
14pub use lexer::ParseError;
15
16/// Parses an environment file that is passed as a `&str`.
17///
18/// Returns an error if if the string is ill-formatted.
19/// Read the [crate's](crate) documentation for information about the
20/// format that `env-file-reader` supports.
21///
22/// **Example:**
23///
24/// ```rust
25/// use env_file_reader::read_str;
26///
27/// const ENV_FILE: &str = "
28/// CLIENT_ID=YOUR_CLIENT_ID
29/// CLIENT_SECRET=YOUR_CLIENT_SECRET
30/// ";
31///
32/// fn main() -> std::io::Result<()> {
33/// let env_variables = read_str(ENV_FILE)?;
34///
35/// assert_eq!(&env_variables["CLIENT_ID"], "YOUR_CLIENT_ID");
36/// assert_eq!(&env_variables["CLIENT_SECRET"], "YOUR_CLIENT_SECRET");
37///
38/// Ok(())
39/// }
40/// ```
41///
42pub fn read_str(s: &str) -> Result<HashMap<String, String>, Error> {
43 env_file::EnvFileParser::new()
44 .parse(lexer::Lexer::new(s))
45 .map_err(|_| Error::new(ErrorKind::InvalidInput, &ParseError))
46}
47
48/// Parses the environment file at the specified `path`.
49///
50/// Returns an error if reading the file was unsuccessful or if the
51/// file is ill-formatted.
52/// Read the [crate's](crate) documentation for information about the
53/// format that `env-file-reader` supports.
54///
55/// **Example:**
56///
57/// `examples/.env`:
58///
59/// ```ini
60/// CLIENT_ID=YOUR_CLIENT_ID
61/// CLIENT_SECRET=YOUR_CLIENT_SECRET
62/// ```
63///
64/// ```rust
65/// use env_file_reader::read_file;
66///
67/// fn main() -> std::io::Result<()> {
68/// let env_variables = read_file("examples/.env")?;
69///
70/// assert_eq!(&env_variables["CLIENT_ID"], "YOUR_CLIENT_ID");
71/// assert_eq!(&env_variables["CLIENT_SECRET"], "YOUR_CLIENT_SECRET");
72///
73/// Ok(())
74/// }
75/// ```
76///
77pub fn read_file<P: AsRef<Path>>(
78 path: P,
79) -> Result<HashMap<String, String>, Error> {
80 let content = fs::read_to_string(path)?;
81
82 read_str(&content)
83}
84
85/// Parses multiple environment files at the specified `paths`
86/// and constructs a single [HashMap] with the merged environment
87/// variables from the files.
88///
89/// This is a vectorized version of [read_file], where a single
90/// output is constructed from calling [read_file] for each file
91/// provided as an argument to this function.
92/// The order in which the `paths` are defined is maintained, so
93/// if an environment file with a higher index exposes the same
94/// environment variable as a file with a lower index, the value of
95/// the file with the higher index is exposed by the returned
96/// [HashMap].
97///
98/// Returns an error if reading one file was unsuccessful or if one
99/// file is ill-formatted.
100/// Read the [crate's](crate) documentation for information about the
101/// format that `env-file-reader` supports.
102///
103/// **Example:**
104///
105/// `examples/.env`:
106///
107/// ```ini
108/// CLIENT_ID=YOUR_CLIENT_ID
109/// CLIENT_SECRET=YOUR_CLIENT_SECRET
110/// ```
111///
112/// `examples/.env.utf8`:
113///
114/// ```ini
115/// 🦄=💖
116/// 💖=🦄
117/// ```
118///
119/// ```rust
120/// use env_file_reader::read_files;
121///
122/// fn main() -> std::io::Result<()> {
123/// let env_variables = read_files(&[
124/// "examples/.env",
125/// "examples/.env.utf8",
126/// ])?;
127///
128/// assert_eq!(&env_variables["CLIENT_ID"], "YOUR_CLIENT_ID");
129/// assert_eq!(&env_variables["CLIENT_SECRET"], "YOUR_CLIENT_SECRET");
130/// assert_eq!(&env_variables["🦄"], "💖");
131/// assert_eq!(&env_variables["💖"], "🦄");
132///
133/// Ok(())
134/// }
135/// ```
136///
137pub fn read_files<P: AsRef<Path>>(
138 paths: &[P],
139) -> Result<HashMap<String, String>, Error> {
140 let mut res = HashMap::new();
141
142 for path in paths {
143 res.extend(read_file(path)?);
144 }
145
146 Ok(res)
147}
148
149#[cfg(test)]
150mod test {
151 use super::read_str;
152
153 #[test]
154 fn wrong_idents() {
155 let s = "key_with#=x";
156 assert!(read_str(s).is_err());
157
158 let s = "key_with`quotes`=x";
159 assert!(read_str(s).is_err());
160
161 let s = "key_with=do-not-work=x";
162 assert!(read_str(s).is_err());
163
164 let s = "key with whitespace=x";
165 assert!(read_str(s).is_err());
166 }
167
168 #[test]
169 fn comments() {
170 let s = "
171 # a comment
172 key=val # a comment at the end of the line
173 ";
174
175 let m = read_str(s).unwrap();
176
177 assert_eq!(&m["key"], "val");
178 }
179
180 #[test]
181 fn empty_value() {
182 let s = "
183 key1=
184 key2=something
185 key3=";
186
187 let m = read_str(s).unwrap();
188
189 assert_eq!(&m["key1"], "");
190 assert_eq!(&m["key2"], "something");
191 assert_eq!(&m["key3"], "");
192 }
193
194 #[test]
195 fn empty_string() {
196 let m = read_str("").unwrap();
197 assert_eq!(m.len(), 0);
198 }
199}