aboutsummaryrefslogtreecommitdiff
path: root/apps/cassiopeia/src/data.rs
diff options
context:
space:
mode:
authorMx Kookie <kookie@spacekookie.de>2020-12-19 15:15:20 +0000
committerMx Kookie <kookie@spacekookie.de>2020-12-21 05:19:49 +0100
commit69eaad1c9f934bccaf7e28529a6b1657345f0184 (patch)
tree4e7517a16f7303f5f6efbd02e4aae85bdc5aec29 /apps/cassiopeia/src/data.rs
parentb9c988f42504c2e4cfa0715ac8f2d2a0db591cad (diff)
cassiopeia: changing internal data representation to timeline module
What this allows us to do is much better relationship tracking between sessions and invoices. The CASS file already has all the structure we need, and it would be silly to replicate it via complicated time association algorithms. This approach uses the linear nature of the data file to track the position relative to other entries. The timeline module is then responsible for making changes to the internal representation (in case it is being used as a library for multi-query commands), and emitting a `Delta` type that can be used to easily patch the IR in question, because the mapping between the timeline and IR representations is linear.
Diffstat (limited to 'apps/cassiopeia/src/data.rs')
-rw-r--r--apps/cassiopeia/src/data.rs142
1 files changed, 36 insertions, 106 deletions
diff --git a/apps/cassiopeia/src/data.rs b/apps/cassiopeia/src/data.rs
index 188c0255203d..3034d020b1e9 100644
--- a/apps/cassiopeia/src/data.rs
+++ b/apps/cassiopeia/src/data.rs
@@ -5,7 +5,9 @@
//! analysis tasks.
use crate::{
+ error::{ParseError, ParseResult, UserResult},
format::ir::{IrItem, IrType, MakeIr},
+ timeline::{Entry, Timeline},
Date, Time,
};
use chrono::{DateTime, Duration, FixedOffset as Offset, Local, NaiveDate};
@@ -14,116 +16,42 @@ use std::collections::BTreeMap;
#[derive(Debug, Default)]
pub struct TimeFile {
/// A parsed header structure
- header: BTreeMap<String, String>,
- /// A parsed session structure
- sessions: Vec<Session>,
- /// A parsed invoice list
- invoices: Vec<Invoice>,
+ pub(crate) header: BTreeMap<String, String>,
+ /// A parsed timeline of events
+ pub(crate) timeline: Timeline,
}
impl TimeFile {
- pub(crate) fn append(&mut self, line: IrItem) {
+ /// Append entries to the timeline from the parsed IR
+ ///
+ /// Report any errors that occur back to the parser, that will
+ /// print a message to the user and terminate the program.
+ pub(crate) fn append(&mut self, line: IrItem) -> ParseResult<()> {
match line {
IrItem {
tt: IrType::Header(ref header),
..
- } => self.header = header.clone(),
+ } => Ok(header.iter().for_each(|(k, v)| {
+ self.header.insert(k.clone(), v.clone());
+ })),
IrItem {
tt: IrType::Start(time),
lo,
- } => self.sessions.push(Session::start(time.into())),
+ } => Ok(self.timeline.start(time).map(|_| ())?),
IrItem {
tt: IrType::Stop(time),
lo,
- } => self.get_last_session().unwrap().stop(time.into()),
+ } => Ok(self.timeline.stop(time).map(|_| ())?),
IrItem {
tt: IrType::Invoice(date),
lo,
- } => self.invoices.push(Invoice::new(date.into())),
- _ => {}
- }
- }
-
- fn get_last_session(&mut self) -> Option<&mut Session> {
- self.sessions.last_mut()
- }
-
- fn get_last_invoice(&mut self) -> Option<&mut Invoice> {
- self.invoices.last_mut()
- }
-
- /// Start a new session (optionally 15-minute rounded)
- ///
- /// This function returns the new session object that will have to
- /// be turned into an IR line to be written back into the file
- pub(crate) fn start(&mut self, round: bool) -> Option<Session> {
- // Check if the last session was closed
- match self.get_last_session() {
- Some(s) if !s.finished() => return None,
- _ => {}
- }
-
- // Create a new time
- let now = if round {
- Time::now().round()
- } else {
- Time::now()
- };
-
- Some(Session::start(now))
- }
-
- /// Stop the last session that was started, returning a completed
- /// session
- pub(crate) fn stop(&mut self, round: bool) -> Option<Session> {
- match self.get_last_session() {
- Some(s) if s.finished() => return None,
- None => return None,
- _ => {}
- }
-
- // Create a new time
- let now = if round {
- Time::now().round()
- } else {
- Time::now()
- };
-
- self.get_last_session().cloned().map(|mut s| {
- s.stop(now);
- s
- })
- }
-
- /// Add a new invoice block to the time file
- pub(crate) fn invoice(&mut self) -> Option<Invoice> {
- let today = Date::today();
-
- let last_sess = self.get_last_session().cloned();
-
- match self.get_last_invoice() {
- // Check if _today_ there has been already an invoice
- Some(i) if i.date == today => return None,
-
- // Check if since the last invoice there has been at least
- // _one_ terminated session.
- Some(i)
- if !last_sess
- .map(|s| !s.stop.map(|s| s.after(&i.date)).unwrap_or(false))
- .unwrap_or(false) =>
- {
- return None
- }
-
- // Otherwise, we create an invoice
- _ => {}
+ } => Ok(self.timeline.invoice(date).map(|_| ())?),
+ _ => Err(ParseError::Unknown),
}
-
- Some(Invoice::new(today))
}
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Session {
start: Time,
stop: Option<Time>,
@@ -131,12 +59,12 @@ pub struct Session {
impl Session {
/// Create a new session with a start time
- fn start(start: Time) -> Self {
+ pub(crate) fn start(start: Time) -> Self {
Self { start, stop: None }
}
/// Finalise a session with a stop time
- fn stop(&mut self, stop: Time) {
+ pub(crate) fn stop(&mut self, stop: Time) {
self.stop = Some(stop);
}
@@ -151,28 +79,30 @@ impl Session {
}
}
-impl MakeIr for Session {
- fn make_ir(&self) -> IrType {
- match self.stop {
- Some(ref time) => IrType::Stop(time.clone()),
- None => IrType::Start(self.start.clone()),
- }
- }
-}
-
-#[derive(Debug)]
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Invoice {
- date: Date,
+ pub(crate) date: Date,
}
impl Invoice {
- fn new(date: Date) -> Self {
+ pub(crate) fn new(date: Date) -> Self {
Self { date }
}
}
-impl MakeIr for Invoice {
+/// Changes to the timeline are encoded in a delta
+pub(crate) enum Delta {
+ Start(Time),
+ Stop(Time),
+ Invoice(Date),
+}
+
+impl MakeIr for Delta {
fn make_ir(&self) -> IrType {
- IrType::Invoice(self.date.clone())
+ match self {
+ Self::Start(ref time) => IrType::Start(time.clone()),
+ Self::Stop(ref time) => IrType::Stop(time.clone()),
+ Self::Invoice(ref date) => IrType::Invoice(date.clone()),
+ }
}
}