aboutsummaryrefslogtreecommitdiff
path: root/apps/koffice/libko/src/cass/format/parser.rs
blob: 8e0602d440d2db80d76e0eda508cf563528c5572 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//! cassiopeia parser
//!
//! Takes a lexer's token stream as an input, and outputs a fully
//! parsed time file.

use crate::cass::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,
}

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!"),
    )
}