aboutsummaryrefslogtreecommitdiff
path: root/apps/koffice/libko/src/cass/format/ir.rs
blob: d1a3a62c15088ce52e990b4d693505aa7f863e97 (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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
use crate::cass::{format::LineCfg, Date, Time, TimeFile};
use std::collections::BTreeMap;

/// A set of IR parsed items that makes up a whole cass file
pub(crate) type IrStream = Vec<IrItem>;

/// Intermediate representation for parsing and generating files
///
/// The CASS IR is largely based on the output of the parser's
/// [`LineCfg`](crate::format::LineCfg), but with concrete types used
/// in the data layer (namely [`Date`][date] and [`Time`][time]),
/// while also keeping track of the line numbers to allow idempotent
/// file changes.
///
/// Something not yet implemented is comment pass-through (this needs
/// to happen in the parser first), but will likely be implemented in
/// a future version.
///
/// [date]: crate::Date
/// [time]: crate::Time
#[derive(Debug, Clone)]
pub(crate) struct IrItem {
    pub(crate) tt: IrType,
    pub(crate) lo: usize,
}

/// Disambiguate between different IR line types with their payload
#[derive(Debug, Clone)]
pub(crate) enum IrType {
    /// A line with parsed header information
    Header(BTreeMap<String, String>),
    /// Start a session at a given timestapm
    Start(Time),
    /// Stop a session at a given timestamp
    Stop(Time),
    /// Invoice a block of previous work
    Invoice(Date),
    /// An item that gets ignored
    Ignore,
}

/// Generate a stream of IR items from the raw parser output
pub(crate) fn generate_ir(buf: impl Iterator<Item = LineCfg>) -> IrStream {
    buf.enumerate().fold(vec![], |mut buf, (lo, item)| {
        #[cfg_attr(rustfmt, rustfmt_skip)]
        buf.push(match item {
            LineCfg::Header(map) => IrItem { tt: IrType::Header(map.into_iter().map(|(k, v)| (k, v.replace(",", ""))).collect()), lo },
            LineCfg::Start(Some(time)) => IrItem { tt: IrType::Start(time.into()), lo },
            LineCfg::Stop(Some(time)) => IrItem { tt: IrType::Stop(time.into()), lo },
            LineCfg::Invoice(Some(date)) => IrItem { tt: IrType::Invoice(date.into()), lo },
            LineCfg::Ignore => IrItem { tt: IrType::Ignore, lo },
            _ => IrItem { tt: IrType::Ignore, lo },
        });

        buf
    })
}

pub(crate) trait MakeIr {
    /// Make a new IR line from an object
    fn make_ir(&self) -> IrType;
}

pub(crate) fn clean_ir(ir: &mut IrStream) {
    ir.remove(0); // FIXME: this is required to remove the leading
                  // comment, which will be manually re-generated at
                  // the moment, but which would just add more blank
                  // lines between the new comment, and the first line
                  // in this current format.  This is very bad, yikes
                  // yikes yikes, but what can I do, I have a deadline
                  // (not really) lol

    // FIXME: this hack gets rid of a trailing empty line if it exists
    // to make sure we don't have any gaps between work sessions.
    if match ir.last() {
        Some(IrItem {
            tt: IrType::Ignore, ..
        }) => true,
        _ => false,
    } {
        ir.pop();
    }
}

/// Taken an IrType and append it to an existing IR stream
pub(crate) fn append_ir(ir: &mut IrStream, tt: IrType) {
    let lo = ir.last().unwrap().lo;
    ir.push(IrItem { tt, lo });
}

/// Search for the header that contains the version string and update it
pub(crate) fn update_header(ir: &mut IrStream) {
    ir.iter_mut().for_each(|item| match item.tt {
        IrType::Header(ref mut map) if map.contains_key("version") => {
            map.insert("version".into(), crate::cass::meta::VERSION.into());
        }
        _ => {}
    });
}