//! Cassiopeia plain text time tracking tool //! //! Versions `0.1` and `0.2` were written in Ruby and are thus //! deprecated. Most likely you are interested in `cass(1)`, the //! simple plain text time tracking utility, part of the kookie-office //! suite of commandline tools! This is the library powering it. //! //! For more documentation, check out: //! https://git.spacekookie.de/kookienomicon/tree/apps/cassiopeia mod data; mod date; mod format; mod time; mod timeline; pub mod error; pub mod meta; pub use date::Date; pub use time::Time; use data::{Invoice, Session, TimeFile}; use error::{ParseError, ParseResult}; use format::{ ir::{append_ir, clean_ir, IrStream, MakeIr}, ParseOutput, }; /// A state handler and primary API for all cass interactions /// /// pub struct Cassiopeia { path: String, tf: TimeFile, ir: IrStream, } impl Cassiopeia { /// Load a cass file from disk, parsing it into a [`TimeFile`](crate::TimeFile) pub fn load(path: &str) -> ParseResult { let path = path.to_owned(); format::load_file(path.as_str()).map(|ParseOutput { tf, ir }| Self { path, tf, ir }) } /// Start a new work session (with optional 15 minute rounding) pub fn start(&mut self, round: bool) -> ParseResult<()> { let delta = self.tf.timeline.start(Time::rounded(round))?; clean_ir(&mut self.ir); append_ir(&mut self.ir, delta.make_ir()); format::write_file(self.path.as_str(), &mut self.ir) } /// Stop the existing work session (with optional 15 minute rounding) pub fn stop(&mut self, round: bool) -> ParseResult<()> { let delta = self.tf.timeline.stop(Time::rounded(round))?; clean_ir(&mut self.ir); append_ir(&mut self.ir, delta.make_ir()); format::write_file(self.path.as_str(), &mut self.ir) } /// Add an invoice block to the time file pub fn invoice<'slf>(&'slf mut self) -> Invoicer<'slf> { Invoicer::new(self) } /// Write out the file IR as is, updating only the header version pub fn update(&mut self) -> ParseResult<()> { clean_ir(&mut self.ir); format::write_file(self.path.as_str(), &mut self.ir) } /// Collect statistics on previous work sessions pub fn stat(&self) -> ParseResult { todo!() } } /// An invoice generator builder /// /// The most simple use-case of this type is to provide no parameters /// and simply add an `INVOICE` line to the cass file. Adittionally /// you may provide the client and project name, which will then /// require the `client_db` path to be set as well. /// /// ```rust,no_run /// # let mut cass = cassiopeia::Cassiopeia::load("").unwrap(); /// cass.invoice().run(); /// ``` /// /// Additional errors can be thrown if the client or project are not /// known in the client db. /// /// ```rust,no_run /// # let mut cass = cassiopeia::Cassiopeia::load("").unwrap(); /// cass.invoice() /// .generate() /// .db("/home/office/clients.yml".into()) /// .client("ACME".into()) /// .run(); /// ``` #[allow(unused)] pub struct Invoicer<'cass> { tf: &'cass mut Cassiopeia, generate: bool, client_db: String, client: String, project: String, } impl<'cass> Invoicer<'cass> { pub fn new(tf: &'cass mut Cassiopeia) -> Self { Self { tf, generate: false, client_db: String::new(), client: String::new(), project: String::new(), } } /// Enable the invoice generation feature pub fn generate(self) -> Self { Self { generate: true, ..self } } /// Provide the client database file (.yml format) pub fn db(self, client_db: String) -> Self { Self { client_db, ..self } } /// Provide the client to invoice pub fn client(self, client: String) -> Self { Self { client, ..self } } pub fn project(self, project: String) -> Self { Self { project, ..self } } pub fn run(mut self) -> ParseResult<()> { if self.generate { eprintln!("Integration with invoice(1) is currently not implemented. Sorry :("); return Err(ParseError::Unknown); } let delta = self.tf.tf.timeline.invoice(Date::today())?; clean_ir(&mut self.tf.ir); append_ir(&mut self.tf.ir, delta.make_ir()); format::write_file(self.tf.path.as_str(), &mut self.tf.ir) } }