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.
82 lines
2.7 KiB
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!"),
|
|
)
|
|
}
|
|
|