aboutsummaryrefslogtreecommitdiff
path: root/apps/koffice/libko/src/cass/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'apps/koffice/libko/src/cass/mod.rs')
-rw-r--r--apps/koffice/libko/src/cass/mod.rs160
1 files changed, 160 insertions, 0 deletions
diff --git a/apps/koffice/libko/src/cass/mod.rs b/apps/koffice/libko/src/cass/mod.rs
new file mode 100644
index 000000000000..43dd79b0fbd7
--- /dev/null
+++ b/apps/koffice/libko/src/cass/mod.rs
@@ -0,0 +1,160 @@
+//! 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;
+
+pub(crate) use data::TimeFile;
+use data::{Invoice, Session};
+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<Self> {
+ let path = path.to_owned();
+ format::load_file(path.as_str()).map(|ParseOutput { tf, ir }| Self { path, tf, ir })
+ }
+
+ pub(crate) fn timefile(&self) -> TimeFile {
+ self.tf.clone()
+ }
+
+ /// 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<String> {
+ 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)
+ }
+}