diff --git a/apps/cassiopeia/src/bin/cass.rs b/apps/cassiopeia/src/bin/cass.rs index 90f84661ae0..8bceddc911a 100644 --- a/apps/cassiopeia/src/bin/cass.rs +++ b/apps/cassiopeia/src/bin/cass.rs @@ -64,6 +64,7 @@ If you want to report a bug, please do so on my mailing list: lists.sr.ht/~space .help(meta::ARG_CLIENT_DB_ABOUT), ) ) + .subcommand(SubCommand::with_name(meta::CMD_STAT).about(meta::CMD_STAT_ABOUT)) .get_matches(); let cass_file = cli.value_of(meta::ARG_FILE).unwrap(); @@ -119,6 +120,13 @@ If you want to report a bug, please do so on my mailing list: lists.sr.ht/~space std::process::exit(1); } }, + (meta::CMD_STAT, _) => match cass.stat() { + Some(s) => println!("{}", s), + None => { + eprintln!("Failed to collect time statistics..."); + std::process::exit(1); + } + }, (_, _) => todo!(), } } diff --git a/apps/cassiopeia/src/data.rs b/apps/cassiopeia/src/data.rs index 188c0255203..3034d020b1e 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, - /// A parsed session structure - sessions: Vec, - /// A parsed invoice list - invoices: Vec, + pub(crate) header: BTreeMap, + /// 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 { - // 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 { - 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 { - 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