1use std::collections::HashMap;
2use std::env;
3use std::io::prelude::*;
4use std::io::BufReader;
5
6use crate::errors::*;
7use crate::parse;
8
9pub struct Iter<R> {
10 lines: QuotedLines<BufReader<R>>,
11 substitution_data: HashMap<String, Option<String>>,
12}
13
14impl<R: Read> Iter<R> {
15 pub fn new(reader: R) -> Iter<R> {
16 Iter {
17 lines: QuotedLines {
18 buf: BufReader::new(reader),
19 },
20 substitution_data: HashMap::new(),
21 }
22 }
23
24 pub fn load(mut self) -> Result<()> {
30 self.remove_bom()?;
31
32 for item in self {
33 let (key, value) = item?;
34 if env::var(&key).is_err() {
35 env::set_var(&key, value);
36 }
37 }
38
39 Ok(())
40 }
41
42 pub fn load_override(mut self) -> Result<()> {
48 self.remove_bom()?;
49
50 for item in self {
51 let (key, value) = item?;
52 env::set_var(key, value);
53 }
54
55 Ok(())
56 }
57
58 fn remove_bom(&mut self) -> Result<()> {
59 let buffer = self.lines.buf.fill_buf().map_err(Error::Io)?;
60 if buffer.starts_with(&[0xEF, 0xBB, 0xBF]) {
62 self.lines.buf.consume(3);
64 }
65 Ok(())
66 }
67}
68
69struct QuotedLines<B> {
70 buf: B,
71}
72
73enum ParseState {
74 Complete,
75 Escape,
76 StrongOpen,
77 StrongOpenEscape,
78 WeakOpen,
79 WeakOpenEscape,
80 Comment,
81 WhiteSpace,
82}
83
84fn eval_end_state(prev_state: ParseState, buf: &str) -> (usize, ParseState) {
85 let mut cur_state = prev_state;
86 let mut cur_pos: usize = 0;
87
88 for (pos, c) in buf.char_indices() {
89 cur_pos = pos;
90 cur_state = match cur_state {
91 ParseState::WhiteSpace => match c {
92 '#' => return (cur_pos, ParseState::Comment),
93 '\\' => ParseState::Escape,
94 '"' => ParseState::WeakOpen,
95 '\'' => ParseState::StrongOpen,
96 _ => ParseState::Complete,
97 },
98 ParseState::Escape => ParseState::Complete,
99 ParseState::Complete => match c {
100 c if c.is_whitespace() && c != '\n' && c != '\r' => ParseState::WhiteSpace,
101 '\\' => ParseState::Escape,
102 '"' => ParseState::WeakOpen,
103 '\'' => ParseState::StrongOpen,
104 _ => ParseState::Complete,
105 },
106 ParseState::WeakOpen => match c {
107 '\\' => ParseState::WeakOpenEscape,
108 '"' => ParseState::Complete,
109 _ => ParseState::WeakOpen,
110 },
111 ParseState::WeakOpenEscape => ParseState::WeakOpen,
112 ParseState::StrongOpen => match c {
113 '\\' => ParseState::StrongOpenEscape,
114 '\'' => ParseState::Complete,
115 _ => ParseState::StrongOpen,
116 },
117 ParseState::StrongOpenEscape => ParseState::StrongOpen,
118 ParseState::Comment => panic!("should have returned early"),
120 };
121 }
122 (cur_pos, cur_state)
123}
124
125impl<B: BufRead> Iterator for QuotedLines<B> {
126 type Item = Result<String>;
127
128 fn next(&mut self) -> Option<Result<String>> {
129 let mut buf = String::new();
130 let mut cur_state = ParseState::Complete;
131 let mut buf_pos;
132 let mut cur_pos;
133 loop {
134 buf_pos = buf.len();
135 match self.buf.read_line(&mut buf) {
136 Ok(0) => match cur_state {
137 ParseState::Complete => return None,
138 _ => {
139 let len = buf.len();
140 return Some(Err(Error::LineParse(buf, len)));
141 }
142 },
143 Ok(_n) => {
144 if buf.trim_start().starts_with('#') {
147 return Some(Ok(String::with_capacity(0)));
148 }
149 let result = eval_end_state(cur_state, &buf[buf_pos..]);
150 cur_pos = result.0;
151 cur_state = result.1;
152
153 match cur_state {
154 ParseState::Complete => {
155 if buf.ends_with('\n') {
156 buf.pop();
157 if buf.ends_with('\r') {
158 buf.pop();
159 }
160 }
161 return Some(Ok(buf));
162 }
163 ParseState::Escape
164 | ParseState::StrongOpen
165 | ParseState::StrongOpenEscape
166 | ParseState::WeakOpen
167 | ParseState::WeakOpenEscape
168 | ParseState::WhiteSpace => {}
169 ParseState::Comment => {
170 buf.truncate(buf_pos + cur_pos);
171 return Some(Ok(buf));
172 }
173 }
174 }
175 Err(e) => return Some(Err(Error::Io(e))),
176 }
177 }
178 }
179}
180
181impl<R: Read> Iterator for Iter<R> {
182 type Item = Result<(String, String)>;
183
184 fn next(&mut self) -> Option<Self::Item> {
185 loop {
186 let line = match self.lines.next() {
187 Some(Ok(line)) => line,
188 Some(Err(err)) => return Some(Err(err)),
189 None => return None,
190 };
191
192 match parse::parse_line(&line, &mut self.substitution_data) {
193 Ok(Some(result)) => return Some(Ok(result)),
194 Ok(None) => {}
195 Err(err) => return Some(Err(err)),
196 }
197 }
198 }
199}