Coverage Report

Created: 2025-10-31 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/dotenvy-0.15.7/src/iter.rs
Line
Count
Source
1
use std::collections::HashMap;
2
use std::env;
3
use std::io::prelude::*;
4
use std::io::BufReader;
5
6
use crate::errors::*;
7
use crate::parse;
8
9
pub struct Iter<R> {
10
    lines: QuotedLines<BufReader<R>>,
11
    substitution_data: HashMap<String, Option<String>>,
12
}
13
14
impl<R: Read> Iter<R> {
15
0
    pub fn new(reader: R) -> Iter<R> {
16
0
        Iter {
17
0
            lines: QuotedLines {
18
0
                buf: BufReader::new(reader),
19
0
            },
20
0
            substitution_data: HashMap::new(),
21
0
        }
22
0
    }
23
24
    /// Loads all variables found in the `reader` into the environment,
25
    /// preserving any existing environment variables of the same name.
26
    ///
27
    /// If a variable is specified multiple times within the reader's data,
28
    /// then the first occurrence is applied.
29
0
    pub fn load(mut self) -> Result<()> {
30
0
        self.remove_bom()?;
31
32
0
        for item in self {
33
0
            let (key, value) = item?;
34
0
            if env::var(&key).is_err() {
35
0
                env::set_var(&key, value);
36
0
            }
37
        }
38
39
0
        Ok(())
40
0
    }
41
42
    /// Loads all variables found in the `reader` into the environment,
43
    /// overriding any existing environment variables of the same name.
44
    ///
45
    /// If a variable is specified multiple times within the reader's data,
46
    /// then the last occurrence is applied.
47
0
    pub fn load_override(mut self) -> Result<()> {
48
0
        self.remove_bom()?;
49
50
0
        for item in self {
51
0
            let (key, value) = item?;
52
0
            env::set_var(key, value);
53
        }
54
55
0
        Ok(())
56
0
    }
57
58
0
    fn remove_bom(&mut self) -> Result<()> {
59
0
        let buffer = self.lines.buf.fill_buf().map_err(Error::Io)?;
60
        // https://www.compart.com/en/unicode/U+FEFF
61
0
        if buffer.starts_with(&[0xEF, 0xBB, 0xBF]) {
62
0
            // remove the BOM from the bufreader
63
0
            self.lines.buf.consume(3);
64
0
        }
65
0
        Ok(())
66
0
    }
67
}
68
69
struct QuotedLines<B> {
70
    buf: B,
71
}
72
73
enum ParseState {
74
    Complete,
75
    Escape,
76
    StrongOpen,
77
    StrongOpenEscape,
78
    WeakOpen,
79
    WeakOpenEscape,
80
    Comment,
81
    WhiteSpace,
82
}
83
84
0
fn eval_end_state(prev_state: ParseState, buf: &str) -> (usize, ParseState) {
85
0
    let mut cur_state = prev_state;
86
0
    let mut cur_pos: usize = 0;
87
88
0
    for (pos, c) in buf.char_indices() {
89
0
        cur_pos = pos;
90
0
        cur_state = match cur_state {
91
0
            ParseState::WhiteSpace => match c {
92
0
                '#' => return (cur_pos, ParseState::Comment),
93
0
                '\\' => ParseState::Escape,
94
0
                '"' => ParseState::WeakOpen,
95
0
                '\'' => ParseState::StrongOpen,
96
0
                _ => ParseState::Complete,
97
            },
98
0
            ParseState::Escape => ParseState::Complete,
99
0
            ParseState::Complete => match c {
100
0
                c if c.is_whitespace() && c != '\n' && c != '\r' => ParseState::WhiteSpace,
101
0
                '\\' => ParseState::Escape,
102
0
                '"' => ParseState::WeakOpen,
103
0
                '\'' => ParseState::StrongOpen,
104
0
                _ => ParseState::Complete,
105
            },
106
0
            ParseState::WeakOpen => match c {
107
0
                '\\' => ParseState::WeakOpenEscape,
108
0
                '"' => ParseState::Complete,
109
0
                _ => ParseState::WeakOpen,
110
            },
111
0
            ParseState::WeakOpenEscape => ParseState::WeakOpen,
112
0
            ParseState::StrongOpen => match c {
113
0
                '\\' => ParseState::StrongOpenEscape,
114
0
                '\'' => ParseState::Complete,
115
0
                _ => ParseState::StrongOpen,
116
            },
117
0
            ParseState::StrongOpenEscape => ParseState::StrongOpen,
118
            // Comments last the entire line.
119
0
            ParseState::Comment => panic!("should have returned early"),
120
        };
121
    }
122
0
    (cur_pos, cur_state)
123
0
}
124
125
impl<B: BufRead> Iterator for QuotedLines<B> {
126
    type Item = Result<String>;
127
128
0
    fn next(&mut self) -> Option<Result<String>> {
129
0
        let mut buf = String::new();
130
0
        let mut cur_state = ParseState::Complete;
131
        let mut buf_pos;
132
        let mut cur_pos;
133
        loop {
134
0
            buf_pos = buf.len();
135
0
            match self.buf.read_line(&mut buf) {
136
0
                Ok(0) => match cur_state {
137
0
                    ParseState::Complete => return None,
138
                    _ => {
139
0
                        let len = buf.len();
140
0
                        return Some(Err(Error::LineParse(buf, len)));
141
                    }
142
                },
143
0
                Ok(_n) => {
144
                    // Skip lines which start with a # before iteration
145
                    // This optimizes parsing a bit.
146
0
                    if buf.trim_start().starts_with('#') {
147
0
                        return Some(Ok(String::with_capacity(0)));
148
0
                    }
149
0
                    let result = eval_end_state(cur_state, &buf[buf_pos..]);
150
0
                    cur_pos = result.0;
151
0
                    cur_state = result.1;
152
153
0
                    match cur_state {
154
                        ParseState::Complete => {
155
0
                            if buf.ends_with('\n') {
156
0
                                buf.pop();
157
0
                                if buf.ends_with('\r') {
158
0
                                    buf.pop();
159
0
                                }
160
0
                            }
161
0
                            return Some(Ok(buf));
162
                        }
163
                        ParseState::Escape
164
                        | ParseState::StrongOpen
165
                        | ParseState::StrongOpenEscape
166
                        | ParseState::WeakOpen
167
                        | ParseState::WeakOpenEscape
168
0
                        | ParseState::WhiteSpace => {}
169
                        ParseState::Comment => {
170
0
                            buf.truncate(buf_pos + cur_pos);
171
0
                            return Some(Ok(buf));
172
                        }
173
                    }
174
                }
175
0
                Err(e) => return Some(Err(Error::Io(e))),
176
            }
177
        }
178
0
    }
179
}
180
181
impl<R: Read> Iterator for Iter<R> {
182
    type Item = Result<(String, String)>;
183
184
0
    fn next(&mut self) -> Option<Self::Item> {
185
        loop {
186
0
            let line = match self.lines.next() {
187
0
                Some(Ok(line)) => line,
188
0
                Some(Err(err)) => return Some(Err(err)),
189
0
                None => return None,
190
            };
191
192
0
            match parse::parse_line(&line, &mut self.substitution_data) {
193
0
                Ok(Some(result)) => return Some(Ok(result)),
194
0
                Ok(None) => {}
195
0
                Err(err) => return Some(Err(err)),
196
            }
197
        }
198
0
    }
199
}