env_file_reader/
lexer.rs

1use logos::{Logos, SpannedIter};
2
3use std::error::Error;
4
5/// The error returned in case an environment fill is ill-formatted.
6///
7/// Read the [crate's](crate) documentation for information about the
8/// format that `env-file-reader` supports.
9///
10/// **Example:**
11///
12/// ```rust
13/// use env_file_reader::read_str;
14///
15/// let err = read_str("badly formatted env file")
16///   .err()
17///   .unwrap();
18///
19/// assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
20/// assert_eq!(err.to_string(), "ParseError");
21/// ```
22///
23#[derive(Debug)]
24pub struct ParseError;
25
26impl std::fmt::Display for ParseError {
27  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28    write!(f, "ParseError")
29  }
30}
31
32impl Error for ParseError {}
33
34fn remove_quotes(lex: &mut logos::Lexer<Token>) -> String {
35  let slice = lex.slice();
36
37  if slice.len() <= 2 {
38    return String::new();
39  }
40
41  let quote = &slice[..1];
42
43  let escaped_quote_pattern = format!("\\{}", quote);
44  let escaped_newline_pattern = "\\n";
45
46  slice[1..slice.len() - 1]
47    .replace(&escaped_quote_pattern, quote)
48    .replace(escaped_newline_pattern, "\n")
49}
50
51#[derive(Logos, Debug, PartialEq, Clone)]
52pub enum Token {
53  #[token("=")]
54  Eq,
55  #[token("export")]
56  Export,
57  #[regex(r"\n\s*")]
58  NewLine,
59  #[regex(r#"[^\s='`"\#]+"#, |lex| lex.slice().parse())]
60  Ident(String),
61  #[regex(r"'[^']*'", remove_quotes)]
62  #[regex(r"`[^`]*`", remove_quotes)]
63  #[regex(r#""([^"]|\\")*""#, remove_quotes)]
64  QuotedString(String),
65  Eof,
66  #[error]
67  #[regex(r"#.*", logos::skip)]
68  #[regex(r"\s+", logos::skip)]
69  Error,
70}
71
72pub(crate) struct Lexer<'input> {
73  token_stream: SpannedIter<'input, Token>,
74  finished: bool,
75}
76
77impl<'input> Lexer<'input> {
78  pub fn new(input: &'input str) -> Self {
79    Self {
80      token_stream: Token::lexer(input).spanned(),
81      finished: false,
82    }
83  }
84
85  fn finish(&mut self) {
86    self.finished = true;
87  }
88
89  fn finished(&self) -> bool {
90    self.finished
91  }
92}
93
94impl<'input> Iterator for Lexer<'input> {
95  type Item = Result<(usize, Token, usize), ParseError>;
96
97  fn next(&mut self) -> Option<Self::Item> {
98    match self.token_stream.next() {
99      Some((token, span)) => match token {
100        Token::Error => Some(Err(ParseError)),
101        _ => Some(Ok((span.start, token, span.end))),
102      },
103      None => {
104        if self.finished() {
105          None
106        } else {
107          self.finish();
108          Some(Ok((0, Token::Eof, 0)))
109        }
110      }
111    }
112  }
113}