//! cassiopeia parser //! //! Takes a lexer's token stream as an input, and outputs a fully //! parsed time file. use crate::format::{LineLexer, LineToken, Token}; use chrono::{DateTime, FixedOffset as Offset, NaiveDate}; use std::collections::BTreeMap; use std::iter::Iterator; /// A type-parsed line in a time file #[derive(Debug)] pub enum LineCfg { /// A header line with a set of keys and values Header(BTreeMap), /// A session start line with a date and time Start(Option>), /// A session stop line with a date and time Stop(Option>), /// An invoice line with a date Invoice(Option), /// A temporary value that is invalid #[doc(hidden)] Ignore, } pub(crate) fn parse<'l>(lex: LineLexer<'l>) -> LineCfg { use LineCfg::*; use Token as T; #[cfg_attr(rustfmt, rustfmt_skip)] lex.get_all().into_iter().fold(Ignore, |cfg, tok| match (cfg, tok) { // If the first token is a comment, we ignore it (Ignore, LineToken { tt: T::Comment, .. }, ) => Ignore, // If the first token is a keyword, we wait for more data (Ignore, LineToken { tt: T::Header, .. }) => Header(Default::default()), (Ignore, LineToken { tt: T::Start, .. }) => Start(None), (Ignore, LineToken { tt: T::Stop, .. }) => Stop(None), (Ignore, LineToken { tt: T::Invoice, .. }) => Invoice(None), // If the first token _was_ a keyword, fill in the data (Header(map), LineToken { tt: T::HeaderData, slice }) => Header(append_data(map, slice)), (Start(_), LineToken { tt: T::Date, slice }) => Start(parse_datetime(slice)), (Stop(_), LineToken { tt: T::Date, slice }) => Stop(parse_datetime(slice)), (Invoice(_), LineToken { tt: T::Date, slice }) => Invoice(parse_date(slice)), // Pass empty lines through, (empty, _) => empty, // Ignore everything else (which will be filtered) _ => Ignore, }) } fn append_data(mut map: BTreeMap, slice: &str) -> BTreeMap { let split = slice.split("=").collect::>(); map.insert(split[0].into(), split[1].into()); map } fn parse_datetime(slice: &str) -> Option> { Some( DateTime::parse_from_str(slice, "%Y-%m-%d %H:%M:%S%:z") .expect("Failed to parse date; invalid format!"), ) } fn parse_date(slice: &str) -> Option { Some( NaiveDate::parse_from_str(slice, "%Y-%m-%d") .expect("Failed to parse date; invalid format!"), ) }