1use std::error::Error;
2use std::fmt::Debug;
3use std::result::Result;
4
5pub struct SyntaxError {
6 pos: usize,
7 msg: String,
8}
9
10impl std::fmt::Display for SyntaxError {
11 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
12 write!(f, "Syntax error at {}: {}", self.pos, self.msg)
13 }
14}
15
16impl std::fmt::Debug for SyntaxError {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 write!(f, "Syntax error at {}: {}", self.pos, self.msg)
19 }
20}
21
22impl Error for SyntaxError {}
23
24#[derive(Debug, Clone)]
25pub struct Span {
26 _start: usize,
27 _end: usize,
28}
29
30#[derive(Debug)]
31pub enum Token {
32 ROOT(Vec<Token>, Option<Span>),
33 Comment(String, Option<Span>),
34 Variable(String, String, Option<Span>),
35}
36
37impl Token {
38 pub fn create_root<T>(tokens: T) -> Token
39 where
40 T: IntoIterator<Item = Token>,
41 {
42 Token::ROOT(tokens.into_iter().collect(), None)
43 }
44
45 pub fn create_comment<T>(comment: T) -> Token
46 where
47 T: ToString,
48 {
49 Token::Comment(comment.to_string(), None)
50 }
51
52 pub fn create_variable<T, U>(name: T, value: U) -> Token
53 where
54 T: ToString,
55 U: ToString,
56 {
57 Token::Variable(name.to_string(), value.to_string(), None)
58 }
59
60 fn append(&mut self, token: Token) {
61 match self {
62 Token::ROOT(tokens, _) => tokens.push(token),
63 _ => {}
64 }
65 }
66
67 pub fn parse<C: AsRef<[u8]>>(content: C) -> Result<Token, Box<dyn Error>> {
68 let mut root_token = Token::ROOT(Vec::new(), None);
69 let content_ref = content.as_ref();
70
71 let mut key_characters: Vec<u8> = vec![];
72
73 key_characters.append(&mut (b'a'..b'z').collect::<Vec<u8>>());
74 key_characters.append(&mut (b'A'..b'Z').collect::<Vec<u8>>());
75 key_characters.append(&mut (b'0'..b'9').collect::<Vec<u8>>());
76 key_characters.push(b'_');
77
78 let mut pos_current = 0;
79 let mut acumulator_type = AcumulatorKind::ROOT;
80 let mut collector_variable_key = String::new();
81 let mut collector_variable_value = String::new();
82 let mut collector_comment = String::new();
83
84 loop {
85 if pos_current >= content_ref.len() {
86 break;
87 }
88 let char = content_ref[pos_current];
89 pos_current += 1;
90
91 match acumulator_type {
92 AcumulatorKind::ROOT => match char {
93 b'#' => {
94 acumulator_type = AcumulatorKind::COMMENT;
95 continue;
96 }
97 b'\n' | b'\t' | b' ' | b'\r' => {
98 continue;
99 }
100 char if key_characters.contains(&char) => {
101 acumulator_type = AcumulatorKind::VariableKey;
102 collector_variable_key = String::from_utf8(vec![char]).unwrap();
103 continue;
104 }
105 _ => {
106 return Err(Box::new(SyntaxError {
107 pos: pos_current,
108 msg: "Unexpected character".to_string(),
109 }));
110 }
111 },
112 AcumulatorKind::VariableValue(AcumulatorVariableValueKind::StartValue) => {
113 match char {
114 b' ' | b'\t' | b'\r' | b'\n' => {
115 continue;
116 }
117 b'"' => {
118 acumulator_type = AcumulatorKind::VariableValue(
119 AcumulatorVariableValueKind::StringDoubleQuote,
120 );
121 collector_variable_value = String::new();
122 continue;
123 }
124 b'\'' => {
125 acumulator_type = AcumulatorKind::VariableValue(
126 AcumulatorVariableValueKind::StringSimpleQuote,
127 );
128 collector_variable_value = String::new();
129 continue;
130 }
131 char if char != b' ' => {
132 acumulator_type = AcumulatorKind::VariableValue(
133 AcumulatorVariableValueKind::StringWithoutQuotes,
134 );
135 collector_variable_value = String::from(char as char);
136 continue;
137 }
138 _ => {
139 return Err(Box::new(SyntaxError {
140 pos: pos_current,
141 msg: "Unexpected character".to_string(),
142 }));
143 }
144 }
145 }
146 AcumulatorKind::VariableValue(AcumulatorVariableValueKind::StringWithoutQuotes) => {
147 match char {
148 b'\n' => {
149 acumulator_type = AcumulatorKind::ROOT;
150 root_token.append(Token::Variable(
151 collector_variable_key.clone(),
152 collector_variable_value.clone().trim().to_string(),
153 None,
154 ));
155 continue;
156 }
157 b'#' => {
158 acumulator_type = AcumulatorKind::COMMENT;
159 root_token.append(Token::Variable(
160 collector_variable_key.clone(),
161 collector_variable_value.clone().trim().to_string(),
162 None,
163 ));
164 continue;
165 }
166 _ => {
167 collector_variable_value.push(char as char);
168 continue;
169 }
170 }
171 }
172 AcumulatorKind::VariableValue(AcumulatorVariableValueKind::StringSimpleQuote) => {
173 match char {
174 b'\'' if content_ref[pos_current - 2] != b'\\' => {
175 acumulator_type = AcumulatorKind::ROOT;
176 root_token.append(Token::Variable(
177 collector_variable_key.clone(),
178 collector_variable_value.replace("\\'", "'").clone(),
179 None,
180 ));
181 continue;
182 }
183 _ => {
184 collector_variable_value.push(char as char);
185 continue;
186 }
187 }
188 }
189 AcumulatorKind::VariableValue(AcumulatorVariableValueKind::StringDoubleQuote) => {
190 match char {
191 b'\"' if content_ref[pos_current - 2] != b'\\' => {
192 acumulator_type = AcumulatorKind::ROOT;
193 root_token.append(Token::Variable(
194 collector_variable_key.clone(),
195 collector_variable_value.replace("\\\"", "\"").clone(),
196 None,
197 ));
198 continue;
199 }
200 _ => {
201 collector_variable_value.push(char as char);
202 continue;
203 }
204 }
205 }
206
207 AcumulatorKind::VariableEqual => match char {
208 b' ' | b'\t' => {
209 continue;
210 }
211 b'=' => {
212 acumulator_type =
213 AcumulatorKind::VariableValue(AcumulatorVariableValueKind::StartValue);
214 continue;
215 }
216 _ => {
217 return Err(Box::new(SyntaxError {
218 pos: pos_current,
219 msg: "Unexpected character".to_string(),
220 }));
221 }
222 },
223 AcumulatorKind::VariableKey => match char {
224 b' ' | b'\t' => {
225 acumulator_type = AcumulatorKind::VariableEqual;
226 continue;
227 }
228 b'=' => {
229 acumulator_type =
230 AcumulatorKind::VariableValue(AcumulatorVariableValueKind::StartValue);
231 continue;
232 }
233 char if key_characters.contains(&char) => {
234 collector_variable_key.push(char as char);
235 continue;
236 }
237 _ => {
238 return Err(Box::new(SyntaxError {
239 pos: pos_current,
240 msg: "Unexpected character".to_string(),
241 }));
242 }
243 },
244 AcumulatorKind::COMMENT => match char {
245 b'\n' => {
246 acumulator_type = AcumulatorKind::ROOT;
247 root_token.append(Token::Comment(
248 collector_comment.clone().trim().to_string(),
249 None,
250 ));
251 continue;
252 }
253 _ => {
254 collector_comment.push(char as char);
255 continue;
256 }
257 },
258 }
259 }
260
261 Ok(root_token)
262 }
263
264 pub fn serialize(token: Token) -> Result<String, Box<String>> {
265 let tokens = match token {
266 Token::ROOT(tokens, _) => tokens,
267 _ => return Err(Box::new("Invalid token require a ROOT token".to_string())),
268 };
269
270 let mut serialized_string = String::new();
271
272 for token in tokens {
273 match token {
274 Token::Variable(key, value, _) => {
275 let next_string = value.replace("'", "\\'");
276
277 serialized_string.push_str(&format!("{}='{}'\n", key, next_string));
278 }
279 Token::Comment(comment, _) => {
280 serialized_string.push_str(&format!("# {}\n", comment));
281 }
282 _ => {}
283 }
284 }
285
286 Ok(serialized_string)
287 }
288}
289
290#[derive(Debug)]
291pub struct EnvMap {}
292
293enum AcumulatorVariableValueKind {
294 StartValue,
295 StringDoubleQuote,
296 StringWithoutQuotes,
297 StringSimpleQuote,
298}
299
300enum AcumulatorKind {
301 ROOT,
302 COMMENT,
303 VariableKey,
304 VariableEqual,
305 VariableValue(AcumulatorVariableValueKind),
306}
307
308impl Default for AcumulatorKind {
309 fn default() -> Self {
310 Self::ROOT
311 }
312}
313
314#[cfg(test)]
315mod tests_env_map {
316 use super::*;
317
318 #[test]
319 fn parse_from_binary() {
320 let payload = b"
321 # Comment
322 key = value
323 KEY = 'val\\'ue' # inline comment
324 KEY2=\"VALUE2\"
325 KEY3 = ABC ASDF # inline comment
326 ";
327
328 let envs = Token::parse(payload).unwrap();
329
330 match envs {
331 Token::ROOT(tokens, _) => {
332 match &tokens[0] {
333 Token::Comment(comment, _) => assert_eq!(comment, "Comment"),
334 _ => panic!("Unexpected token"),
335 }
336
337 match &tokens[1] {
338 Token::Variable(key, value, _) => {
339 assert_eq!(key, "key");
340 assert_eq!(value, "value");
341 }
342 _ => panic!("Unexpected token"),
343 }
344
345 match &tokens[2] {
346 Token::Variable(key, value, _) => {
347 assert_eq!(key, "KEY");
348 assert_eq!(value, "val'ue");
349 }
350 _ => panic!("Unexpected token"),
351 }
352
353 match &tokens[4] {
354 Token::Variable(key, value, _) => {
355 assert_eq!(key, "KEY2");
356 assert_eq!(value, "VALUE2");
357 }
358 _ => panic!("Unexpected token"),
359 }
360
361 match &tokens[5] {
362 Token::Variable(key, value, _) => {
363 assert_eq!(key, "KEY3");
364 assert_eq!(value, "ABC ASDF");
365 }
366 _ => panic!("Unexpected token"),
367 }
368 }
369 _ => panic!("Unexpected token"),
370 }
371 }
372
373 #[test]
374 fn serialize_to_binary() {
375 let payload = Token::create_root([
376 Token::create_comment("Comment"),
377 Token::create_variable("KEY1", "value"),
378 Token::create_variable("KEY2", "VALUE2"),
379 Token::create_variable("KEY3", "ABC ASDF"),
380 ]);
381
382 let body_payload = Token::serialize(payload).unwrap();
383
384 assert_eq!(
385 "# Comment\nKEY1='value'\nKEY2='VALUE2'\nKEY3='ABC ASDF'\n",
386 body_payload
387 );
388 }
389}