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/koffice/libko/src/cass/timeline.rs

132 lines
4.3 KiB

use crate::cass::{
data::{Delta, Invoice, Session},
error::{UserError, UserResult},
Date, Time,
};
/// A timeline entry of sessions and invoices
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Entry {
Session(Session),
Invoice(Invoice),
}
impl From<Session> for Entry {
fn from(s: Session) -> Self {
Self::Session(s)
}
}
impl From<Invoice> for Entry {
fn from(i: Invoice) -> Self {
Self::Invoice(i)
}
}
/// A timeline of sessions and invoices, ordered chronologically
#[derive(Debug, Default, Clone)]
pub struct Timeline {
inner: Vec<Entry>,
}
impl Timeline {
/// Take a set of sessions and invoices to sort into a timeline
pub fn build(s: Vec<Session>, i: Vec<Invoice>) -> Self {
let mut inner: Vec<_> = s.into_iter().map(|s| Entry::Session(s)).collect();
inner.append(&mut i.into_iter().map(|i| Entry::Invoice(i)).collect());
Self { inner }
}
/// Utility function to get the last session in the timeline
fn last_session(&mut self) -> Option<&mut Session> {
self.inner
.iter_mut()
.find(|e| match e {
Entry::Session(_) => true,
_ => false,
})
.map(|e| match e {
Entry::Session(ref mut s) => s,
_ => unreachable!(),
})
}
/// Utility function to get the last invoice in the timeline
fn last_invoice(&self) -> Option<&Invoice> {
self.inner
.iter()
.find(|e| match e {
Entry::Invoice(_) => true,
_ => false,
})
.map(|e| match e {
Entry::Invoice(ref s) => s,
_ => unreachable!(),
})
}
/// Get a list of sessions that happened up to a certain invoice date
///
/// **WARNING** If there is no invoice with the given date, this
/// function will return garbage data, so don't call it with
/// invoice dates that don't exist.
///
/// Because: if the date passes other invoices on the way, the accumulator
/// will be discarded and a new count will be started.
pub fn session_iter(&self, date: &Date) -> Vec<&Session> {
self.inner
.iter()
.fold((false, vec![]), |(mut done, mut acc), entry| {
match (done, entry) {
// Put sessions into the accumulator
(false, Entry::Session(ref s)) => acc.push(s),
// When we reach the target invoice, terminate the iterator
(false, Entry::Invoice(ref i)) if &i.date == date => done = true,
// When we hit another invoice, empty accumulator
(false, Entry::Invoice(_)) => acc.clear(),
// When we are ever "done", skip all other entries
(true, _) => {}
}
(done, acc)
})
.1
}
/// Start a new session, if no active session is already in progress
pub fn start(&mut self, time: Time) -> UserResult<Delta> {
match self.last_session() {
Some(s) if !s.finished() => Err(UserError::ActiveSessionExists),
_ => Ok(()),
}?;
self.inner.push(Session::start(time.clone()).into());
Ok(Delta::Start(time))
}
/// Stop an ongoing session, if one exists
pub fn stop(&mut self, time: Time) -> UserResult<Delta> {
match self.last_session() {
Some(s) if s.finished() => Err(UserError::NoActiveSession),
_ => Ok(()),
}?;
self.last_session().unwrap().stop(time.clone());
Ok(Delta::Stop(time))
}
/// Create a new invoice on the given day
pub fn invoice(&mut self, date: Date) -> UserResult<Delta> {
match self.last_invoice() {
// If an invoice on the same day exists already
Some(i) if i.date == date => Err(UserError::SameDayInvoice),
// If there was no work since the last invoice
Some(ref i) if self.session_iter(&i.date).len() == 0 => Err(UserError::NoWorkInvoice),
// Otherwise everything is coolio
_ => Ok(()),
}?;
self.inner.push(Invoice::new(date.clone()).into());
Ok(Delta::Invoice(date))
}
}