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}