aws-unlock 0.1.0

Unlock your AWS profile as needed
Documentation
use anyhow::{bail, Ok, Result};
use std::{collections::HashMap, iter::from_fn};

use crate::line_lexer::EntryLine;

#[derive(Debug, Clone)]
pub struct EntryLineParser<'a> {
    lines: Vec<EntryLine<'a>>,
    index: usize,
}

#[derive(Debug, Clone)]
pub struct Entry {
    pub comments: Vec<String>,
    pub is_production: bool,
    pub is_locked: bool,
    pub header: String,
    pub values: HashMap<String, String>,
}

impl<'a> EntryLineParser<'a> {
    pub fn new(lines: Vec<EntryLine<'a>>) -> Self {
        Self { lines, index: 0 }
    }

    pub fn parse(&mut self) -> Result<Vec<Entry>> {
        from_fn(|| self.parse_one().transpose()).collect()
    }

    pub fn parse_one(&mut self) -> Result<Option<Entry>> {
        self.skip_empty_line();
        if self.is_finished() {
            return Ok(None);
        }

        let mut all_comments = vec![];
        let (comments, is_production) = self.parse_is_production()?;
        all_comments.extend(comments);

        let (comments, is_locked) = self.parse_is_locked()?;
        all_comments.extend(comments);

        let (comments, header) = self.parse_header(is_locked)?;
        all_comments.extend(comments);

        let (comments, values) = self.parse_values(is_locked)?;
        all_comments.extend(comments);

        Ok(Some(Entry {
            comments: all_comments,
            is_production,
            is_locked,
            header,
            values,
        }))
    }

    fn parse_is_production(&mut self) -> Result<(Vec<String>, bool)> {
        let mut comments = vec![];
        while let Some(line) = self.peek_line() {
            match line {
                EntryLine::Empty => {
                    self.next_line().unwrap();
                    continue;
                }
                EntryLine::Comment(comment) => {
                    let comment = comment.to_string();
                    self.next_line().unwrap();
                    comments.push(comment);
                    continue;
                }
                EntryLine::ProductionMarker => {
                    self.next_line().unwrap();
                    return Ok((comments, true));
                }
                _ => return Ok((comments, false)),
            }
        }

        bail!("unexpected EOF while scanning is_production");
    }

    fn parse_is_locked(&mut self) -> Result<(Vec<String>, bool)> {
        let mut comments = vec![];
        while let Some(line) = self.peek_line() {
            match line {
                EntryLine::Empty => {
                    self.next_line().unwrap();
                    continue;
                }
                EntryLine::Comment(comment) => {
                    let comment = comment.to_string();
                    self.next_line().unwrap();
                    comments.push(comment);
                    continue;
                }
                EntryLine::ProductionMarker => bail!("unexpected production marker"),
                EntryLine::LockedHeader(_) | EntryLine::LockedOption(_, _) => {
                    return Ok((comments, true))
                }
                EntryLine::Header(_) | EntryLine::Option(_, _) => return Ok((comments, false)),
            }
        }

        bail!("unexpected EOF while scanning is_locked");
    }

    fn parse_header(&mut self, is_locked: bool) -> Result<(Vec<String>, String)> {
        let mut comments = vec![];
        while let Some(line) = self.peek_line() {
            match line {
                EntryLine::Empty => {
                    self.next_line().unwrap();
                    continue;
                }
                EntryLine::Comment(comment) => {
                    let comment = comment.to_string();
                    self.next_line().unwrap();
                    comments.push(comment);
                    continue;
                }
                EntryLine::ProductionMarker => {
                    bail!("unexpected production marker while parsing header")
                }
                EntryLine::Header(header) if !is_locked => {
                    let header = header.to_string();
                    self.next_line().unwrap();
                    return Ok((comments, header));
                }
                EntryLine::LockedHeader(header) if is_locked => {
                    let header = header.to_string();
                    self.next_line().unwrap();
                    return Ok((comments, header));
                }
                _ => bail!("unexpected line while scanning header"),
            }
        }

        bail!("unexpected EOF while scanning header");
    }

    fn parse_values(&mut self, is_locked: bool) -> Result<(Vec<String>, HashMap<String, String>)> {
        let mut values = HashMap::new();
        while let Some(line) = self.peek_line() {
            match line {
                EntryLine::Empty => {
                    self.next_line().unwrap();
                    continue;
                }
                EntryLine::Option(key, value) if !is_locked => {
                    let key = key.to_string();
                    let value = value.to_string();
                    self.next_line().unwrap();
                    values.insert(key, value);
                }
                EntryLine::LockedOption(key, value) => {
                    let key = key.to_string();
                    let value = value.to_string();
                    self.next_line().unwrap();
                    values.insert(key, value);
                }
                EntryLine::ProductionMarker
                | EntryLine::Comment(_)
                | EntryLine::Header(_)
                | EntryLine::LockedHeader(_) => return Ok((vec![], values)),
                _ => bail!("unexpected line while scanning values"),
            }
        }

        Ok((vec![], values))
    }

    fn skip_empty_line(&mut self) {
        while self.peek_line() == Some(&EntryLine::Empty) {
            self.next_line().unwrap();
        }
    }

    fn is_finished(&self) -> bool {
        self.index == self.lines.len()
    }

    fn peek_line(&self) -> Option<&EntryLine> {
        self.lines.get(self.index)
    }

    fn next_line(&mut self) -> Option<&EntryLine> {
        let res = self.lines.get(self.index);
        if self.index < self.lines.len() {
            self.index += 1;
        }
        res
    }
}