My personal project and infrastructure archive
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
nomicon/apps/cassiopeia/src/format/parser.rs

82 lines
2.7 KiB

//! 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<String, String>),
/// A session start line with a date and time
Start(Option<DateTime<Offset>>),
/// A session stop line with a date and time
Stop(Option<DateTime<Offset>>),
/// An invoice line with a date
Invoice(Option<NaiveDate>),
/// A temporary value that is invalid
#[doc(hidden)]
Ignore,
}
impl LineCfg {
pub(crate) fn valid(&self) -> bool {
match self {
LineCfg::Ignore => false,
_ => true,
}
}
}
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<String, String>, slice: &str) -> BTreeMap<String, String> {
let split = slice.split("=").collect::<Vec<_>>();
map.insert(split[0].into(), split[1].into());
map
}
fn parse_datetime(slice: &str) -> Option<DateTime<Offset>> {
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<NaiveDate> {
Some(
NaiveDate::parse_from_str(slice, "%Y-%m-%d")
.expect("Failed to parse date; invalid format!"),
)
}