aws_unlock/
line_lexer.rs

1use anyhow::{bail, Ok, Result};
2
3#[derive(Debug, Clone)]
4pub struct EntryLineLexer<'a> {
5    lines: Vec<&'a str>,
6    index: usize,
7}
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10pub enum EntryLine<'a> {
11    Empty,
12    Comment(&'a str),
13    ProductionMarker,
14    Header(&'a str),
15    Option(&'a str, &'a str),
16    LockedHeader(&'a str),
17    LockedOption(&'a str, &'a str),
18}
19
20impl<'a> EntryLineLexer<'a> {
21    pub fn new(contents: &'a str) -> Self {
22        let lines = contents.lines().collect();
23        Self { lines, index: 0 }
24    }
25
26    pub fn tokenize(&mut self) -> Result<Vec<EntryLine<'a>>> {
27        let mut res = vec![];
28        while let Some(line) = self.next_line() {
29            if line.trim().starts_with('#') {
30                res.push(tokenize_commented(line));
31            } else {
32                res.push(tokenize_uncommented(line)?);
33            }
34        }
35
36        Ok(res)
37    }
38
39    fn next_line(&mut self) -> Option<&'a str> {
40        let res = self.lines.get(self.index).copied();
41        if self.index < self.lines.len() {
42            self.index += 1;
43        }
44        res
45    }
46}
47
48fn tokenize_commented(line: &str) -> EntryLine {
49    let trimmed = line.trim()[1..].trim();
50    if trimmed == "production" {
51        EntryLine::ProductionMarker
52    } else if trimmed.starts_with('[') && trimmed.ends_with(']') {
53        // Header
54        EntryLine::LockedHeader(&trimmed[1..trimmed.len() - 1])
55    } else if trimmed.contains('=') {
56        // Option
57        let [key, value]: [&str; 2] = trimmed
58            .splitn(2, '=')
59            .collect::<Vec<_>>()
60            .try_into()
61            .expect("should always be splitted to two entries");
62        let key = key.trim_end();
63        let value = value.trim_start();
64        EntryLine::LockedOption(key, value)
65    } else {
66        // Simple Comment
67        let start = if line.trim().starts_with("# ") { 2 } else { 1 };
68        EntryLine::Comment(&line[start..])
69    }
70}
71
72fn tokenize_uncommented(line: &str) -> Result<EntryLine> {
73    if line.starts_with('[') && line.ends_with(']') {
74        // Header
75        Ok(EntryLine::Header(&line[1..line.len() - 1]))
76    } else if line.contains('=') {
77        // Option
78        let [key, value]: [&str; 2] = line
79            .splitn(2, '=')
80            .collect::<Vec<_>>()
81            .try_into()
82            .expect("should always be splitted to two entries");
83        let key = key.trim_end();
84        let value = value.trim_start();
85        Ok(EntryLine::Option(key, value))
86    } else if line.trim() == "" {
87        Ok(EntryLine::Empty)
88    } else {
89        bail!("unexpected line: {:?}", line);
90    }
91}