1#![allow(clippy::needless_doctest_main)]
2use std::{error::Error, fmt::Display, fs::File, io::BufRead, path::Path};
31#[derive(Debug)]
32pub struct SimpleEnvError {
33 pub kind: String,
34 message: String,
35 pub list: Option<Vec<(String, String)>>,
36}
37
38impl Display for SimpleEnvError {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 write!(f, "{}: {}", self.kind, self.message)
41 }
42}
43impl Error for SimpleEnvError {
44 fn source(&self) -> Option<&(dyn Error + 'static)> {
45 None
46 }
47}
48impl From<std::io::Error> for SimpleEnvError {
49 fn from(error: std::io::Error) -> Self {
50 SimpleEnvError {
51 kind: String::from("io"),
52 message: error.to_string(),
53 list: None,
54 }
55 }
56}
57
58impl From<SimpleEnvError> for std::io::Error {
72 fn from(err: SimpleEnvError) -> Self {
73 std::io::Error::other(err.to_string())
74 }
75}
76
77pub fn to_env() -> Result<(), SimpleEnvError> {
91 match read(".env") {
92 Ok(list) => {
93 iter_to_env(&list);
94 Ok(())
95 }
96 Err(e) => {
97 e.list.as_ref().map(iter_to_env);
98 Err(e)
99 }
100 }
101}
102
103pub fn to_env_override() -> Result<(), SimpleEnvError> {
117 match read(".env") {
118 Ok(list) => {
119 iter_to_env_override(&list);
120 Ok(())
121 }
122 Err(e) => {
123 e.list.as_ref().map(iter_to_env_override);
124 Err(e)
125 }
126 }
127}
128
129fn iter_to_env(list: &Vec<(String, String)>) {
130 for line in list {
131 let (key, value) = (&line.0, &line.1);
132 if std::env::var(key).is_err() {
133 std::env::set_var(key, value);
134 }
135 }
136}
137
138fn iter_to_env_override(list: &Vec<(String, String)>) {
139 for line in list {
140 let (key, value) = (&line.0, &line.1);
141 std::env::set_var(key, value);
142 }
143}
144pub fn to_vec() -> Result<Vec<(String, String)>, SimpleEnvError> {
164 let list = read(".env")?;
165 Ok(list)
166}
167
168pub fn file_to_env<P: AsRef<Path>>(path: P) -> Result<(), Box<dyn std::error::Error>> {
174 let list = read(path)?;
175 for line in list {
176 let (key, value) = (line.0, line.1);
177 if std::env::var(&key).is_err() {
178 std::env::set_var(key, value);
179 }
180 }
181 Ok(())
182}
183
184pub fn file_to_env_override<P: AsRef<Path>>(path: P) -> Result<(), Box<dyn std::error::Error>> {
190 let list = read(path)?;
191 for line in list {
192 let (key, value) = (line.0, line.1);
193 std::env::set_var(key, value);
194 }
195 Ok(())
196}
197
198pub fn file_to_vec<P: AsRef<Path>>(
207 path: P,
208) -> Result<Vec<(String, String)>, Box<dyn std::error::Error>> {
209 let list = read(path)?;
210 Ok(list)
211}
212
213pub fn get_or(key: &str, default: &str) -> String {
221 std::env::var(key).unwrap_or_else(|_| default.to_owned())
222}
223
224fn read<P: AsRef<Path>>(path: P) -> Result<Vec<(String, String)>, SimpleEnvError> {
225 let f = File::open(path)?;
226 let lines = std::io::BufReader::new(f).lines();
227 parse(lines)
228}
229
230fn parse(
231 lines: impl Iterator<Item = Result<String, std::io::Error>>,
232) -> Result<Vec<(String, String)>, SimpleEnvError> {
233 let mut error_lines = Vec::new();
234 let mut num_error_lines = 0;
235 let mut list = Vec::new();
236 let lines = lines;
237 for (col, line) in lines.enumerate() {
238 let line = line?;
239 let line = line.trim();
240 if line.starts_with('#') || line.is_empty() {
241 continue;
242 }
243 let parsed = match parse_line(line) {
244 Ok(parsed) => parsed,
245 Err(e) => {
246 num_error_lines += 1;
247 if error_lines.len() < 10 {
248 error_lines.push(format!("Error in Line {col}: {e}"));
249 }
250 continue;
251 }
252 };
253 list.push((parsed.0.to_owned(), parsed.1.to_owned()));
254 }
255 if error_lines.is_empty() {
256 Ok(list)
257 } else {
258 if num_error_lines > error_lines.len() {
259 error_lines.push(format!(
260 "And {} more errors in .env file",
261 num_error_lines - error_lines.len()
262 ));
263 }
264 Err(SimpleEnvError {
265 kind: "LinesError".to_string(),
266 message: error_lines.join("\n"),
267 list: Some(list),
268 })
269 }
270}
271
272fn parse_line(s: &str) -> Result<(&str, &str), Box<dyn Error>> {
273 if s.is_empty() {
274 return Err("Empty line".into());
275 }
276 if s.starts_with('#') {
277 return Err("Comment line".into());
278 }
279 let mut name_begin: usize = 0;
280 let mut name_end: usize = 0;
281 let mut value_begin: usize = 0;
282 let mut value_end: usize = 0;
283 let mut in_name = true;
284 let mut in_value = false;
285 let mut quotes = 'f';
286 let mut must_trim = false;
287 for (pos, c) in s.char_indices() {
288 match c {
289 '"' | '\'' | '`' => {
290 if quotes != 'f' {
291 if quotes == c {
293 quotes = 'f';
295 if in_name {
296 name_end = pos - 1;
297 }
298 if in_value {
299 value_end = pos - 1;
300 break; }
302 }
303 } else {
304 quotes = c;
306 if in_name {
307 name_begin = pos + 1;
308 }
309 if in_value {
310 value_begin = pos + 1;
311 }
312 }
313 }
314 '=' => {
315 if quotes != 'f' {
316 continue;
317 }
318 if in_name {
319 in_name = false;
320 in_value = true;
321 if name_end == 0 && pos > 0 {
322 name_end = pos - 1;
323 }
324 }
325 }
326 '#' => {
327 if quotes != 'f' {
328 continue;
329 }
330 if in_value {
331 value_end = pos - 1;
332 must_trim = true;
333 break;
334 }
335 if in_name {
336 return Err(format!("Comment character '#' in name part of '{s}'").into());
337 }
338 }
339 _ => {
340 if in_name {
341 if c.is_whitespace() && quotes == 'f' {
342 continue;
343 }
344 name_end = pos;
345 } else if c.is_whitespace() && quotes == 'f' {
346 continue;
347 }
348 if in_value {
349 value_end = pos;
350 if value_begin == 0 {
351 value_begin = pos;
352 }
353 }
354 }
355 }
356 }
357 if value_begin == 0 || name_end == 0 {
358 Err(format!("No name or value in '{s}'").into())
359 } else if value_begin == 0 {
360 Err("No value".into())
361 } else {
362 if must_trim {
363 {
364 let s = &s[value_begin..=value_end];
365 value_end = value_begin + s.trim_end().len() - 1;
366 }
367 }
368 Ok((&s[name_begin..=name_end], &s[value_begin..=value_end]))
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375
376 #[test]
377 fn it_works() {
378 match to_env() {
379 Ok(_) => {
380 assert!(false)
382 }
383 Err(e) => {
384 assert_eq!(e.kind, "LinesError");
386 assert_eq!(
387 e.message.starts_with("Error in Line ")
388 && e.message.ends_with("No name or value in 'error='"),
389 true
390 );
391 }
392 }
393 }
394
395 #[test]
396 fn test_parse_line_new() {
397 assert_eq!(parse_line("FOO=BAR").unwrap(), ("FOO", "BAR"));
398 assert_eq!(parse_line("\"FOO\"=\"BAR\"").unwrap(), ("FOO", "BAR"));
399 assert_eq!(parse_line("FOO = BAR").unwrap(), ("FOO", "BAR"));
400 assert_eq!(parse_line("FOO=\"BAR\"").unwrap(), ("FOO", "BAR"));
401 assert_eq!(parse_line("FOO='BAR'").unwrap(), ("FOO", "BAR"));
402 assert_eq!(parse_line("FOO=`BAR`").unwrap(), ("FOO", "BAR"));
403 assert_eq!(parse_line("FOO=\t `BAR`").unwrap(), ("FOO", "BAR"));
404 assert_eq!(parse_line("FOO\t=\t `BAR`").unwrap(), ("FOO", "BAR"));
405 assert_eq!(parse_line("FOO\t=\t ` BAR`").unwrap(), ("FOO", " BAR"));
406 assert_eq!(
407 parse_line("FOO\t=\t ` BAR`#comment").unwrap(),
408 ("FOO", " BAR")
409 );
410 assert_eq!(parse_line("FOO\t=\t ` BAR `").unwrap(), ("FOO", " BAR "));
411 assert_eq!(
412 parse_line("FOO\t = \t ` BAR `").unwrap(),
413 ("FOO", " BAR ")
414 );
415 assert_eq!(
416 parse_line(" FOO\t = \t ` BAR `").unwrap(),
417 (" FOO", " BAR ")
418 );
419
420 assert_eq!(true, matches!(parse_line(" FOO\t = "), Result::Err(_)));
421 assert_eq!(true, matches!(parse_line(" FOO\t ="), Result::Err(_)));
422 assert_eq!(true, matches!(parse_line("="), Result::Err(_)));
423 assert_eq!(true, matches!(parse_line("#value=comment"), Result::Err(_)));
424 }
425
426 #[test]
427 fn test_parse() {
428 let env_sim = r#"
429FOO=BAR
430# comment
431FOO2= BAR2
432
433FOO3="BAR3"
434FOO4='BAR4'
435FOO5=`BAR5`
436FOO6=BAR6 #comment
437Foo7=BA😀R7 #comment
438#FOO8=BAR8
439FOO9=BAR9
440"#;
441 let lines = env_sim.lines().map(|s| Ok(s.to_owned()));
442 let lines_clone = lines.clone();
443 let start = std::time::Instant::now();
444 let list = parse(lines).unwrap();
445 let time_old = start.elapsed();
446 let start = std::time::Instant::now();
447 let list2 = parse(lines_clone).unwrap();
448 println!("Old: {:?} New {:?}", time_old, start.elapsed());
449 assert_eq!(
450 list,
451 vec![
452 ("FOO".to_owned(), "BAR".to_owned()),
453 ("FOO2".to_owned(), "BAR2".to_owned()),
454 ("FOO3".to_owned(), "BAR3".to_owned()),
455 ("FOO4".to_owned(), "BAR4".to_owned()),
456 ("FOO5".to_owned(), "BAR5".to_owned()),
457 ("FOO6".to_owned(), "BAR6".to_owned()),
458 ("Foo7".to_owned(), "BA😀R7".to_owned()),
459 ("FOO9".to_owned(), "BAR9".to_owned()),
460 ]
461 );
462 assert_eq!(list, list2);
463 }
464
465 #[test]
466 fn parse_reports_each_error_once() {
467 let env_sim = "FOO=BAR\ninvalid\ninvalid2\n";
468 let lines = env_sim.lines().map(|s| Ok(s.to_owned()));
469 let err = parse(lines).unwrap_err();
470 let messages: Vec<&str> = err.message.lines().collect();
471
472 assert_eq!(messages.len(), 2);
473 assert_eq!(
474 messages
475 .iter()
476 .filter(|line| line.contains("Error in Line 1:"))
477 .count(),
478 1
479 );
480 assert_eq!(
481 messages
482 .iter()
483 .filter(|line| line.contains("Error in Line 2:"))
484 .count(),
485 1
486 );
487 }
488
489 #[test]
490 fn test_iter_to_env_does_not_override() {
491 let key = "TEST_NO_OVERRIDE_KEY";
492 let original_value = "original_value";
493 let new_value = "new_value";
494
495 std::env::set_var(key, original_value);
497
498 let list = vec![(key.to_owned(), new_value.to_owned())];
500 iter_to_env(&list);
501
502 assert_eq!(std::env::var(key).unwrap(), original_value);
504
505 std::env::remove_var(key);
507 }
508
509 #[test]
510 fn test_iter_to_env_sets_new_var() {
511 let key = "TEST_NEW_VAR_KEY";
512 let value = "test_value";
513
514 std::env::remove_var(key);
516
517 let list = vec![(key.to_owned(), value.to_owned())];
519 iter_to_env(&list);
520
521 assert_eq!(std::env::var(key).unwrap(), value);
523
524 std::env::remove_var(key);
526 }
527
528 #[test]
529 fn test_iter_to_env_override_overrides() {
530 let key = "TEST_OVERRIDE_KEY";
531 let original_value = "original_value";
532 let new_value = "new_value";
533
534 std::env::set_var(key, original_value);
536
537 let list = vec![(key.to_owned(), new_value.to_owned())];
539 iter_to_env_override(&list);
540
541 assert_eq!(std::env::var(key).unwrap(), new_value);
543
544 std::env::remove_var(key);
546 }
547}