From 4c97f3208a0ba185264a169e01d0b0d922266ea6 Mon Sep 17 00:00:00 2001 From: Mx Kookie Date: Sat, 12 Dec 2020 11:24:43 +0000 Subject: cassiopeia: adding API outline --- apps/cassiopeia/src/bin/cass.rs | 9 ++- apps/cassiopeia/src/data.rs | 9 ++- apps/cassiopeia/src/format/mod.rs | 22 ++++--- apps/cassiopeia/src/lib.rs | 121 ++++++++++++++++++++++++++++++++++++++ apps/cassiopeia/src/meta.rs | 8 ++- 5 files changed, 157 insertions(+), 12 deletions(-) (limited to 'apps/cassiopeia') diff --git a/apps/cassiopeia/src/bin/cass.rs b/apps/cassiopeia/src/bin/cass.rs index cb799d18ccc5..45b032d7d768 100644 --- a/apps/cassiopeia/src/bin/cass.rs +++ b/apps/cassiopeia/src/bin/cass.rs @@ -9,6 +9,7 @@ fn main() { at https://git.spacekookie.de/kookienomicon/tree/apps/cassiopeia If you want to report a bug, please do so on my mailing list: lists.sr.ht/~spacekookie/public-inbox") .author(meta::AUTHOR) + .max_term_width(120) .setting(clap::AppSettings::SubcommandRequiredElseHelp) .global_settings(&[ clap::AppSettings::DisableHelpSubcommand, @@ -54,7 +55,13 @@ If you want to report a bug, please do so on my mailing list: lists.sr.ht/~space .short("g") .long("gen") .help(meta::ARG_GEN_YAML_ABOUT), - ), + ) + .arg( + Arg::with_name(meta::ARG_CLIENT_DB) + .long("client-db") + .takes_value(true) + .help(meta::ARG_CLIENT_DB_ABOUT), + ) ) .get_matches(); diff --git a/apps/cassiopeia/src/data.rs b/apps/cassiopeia/src/data.rs index 3911345109ca..7442d699be88 100644 --- a/apps/cassiopeia/src/data.rs +++ b/apps/cassiopeia/src/data.rs @@ -5,7 +5,7 @@ //! analysis tasks. use crate::format::LineCfg; -use chrono::{DateTime, Duration, FixedOffset as Offset, NaiveDate}; +use chrono::{DateTime, Duration, Local, FixedOffset as Offset, NaiveDate}; use std::collections::BTreeMap; #[derive(Debug, Default)] @@ -38,6 +38,13 @@ impl TimeFile { fn get_last_session(&mut self) -> &mut Session { self.sessions.last_mut().unwrap() } + + /// Start a new session (optionally 15-minute rounded) + pub fn start(&mut self, round: bool) -> Option<()> { + let now = Local::now(); + + Some(()) + } } #[derive(Debug)] diff --git a/apps/cassiopeia/src/format/mod.rs b/apps/cassiopeia/src/format/mod.rs index b5342d62da13..e55658d515ff 100644 --- a/apps/cassiopeia/src/format/mod.rs +++ b/apps/cassiopeia/src/format/mod.rs @@ -9,17 +9,21 @@ pub(crate) use parser::LineCfg; use crate::TimeFile; use std::{fs::File, io::Read}; -pub fn load_file(path: &str) -> TimeFile { - let mut f = File::open(path).unwrap(); +/// Load a file from disk and parse it into a +/// [`TimeFile`](crate::TimeFile) +pub fn load_file(path: &str) -> Option { + let mut f = File::open(path).ok()?; let mut content = String::new(); - f.read_to_string(&mut content).unwrap(); + f.read_to_string(&mut content).ok()?; let mut lines: Vec = content.split("\n").map(|l| l.to_owned()).collect(); - lines - .iter_mut() - .map(|line| lexer::lex(line)) - .map(|lex| parser::parse(lex)) - .filter(|line| line.valid()) - .fold(TimeFile::default(), |file, line| file.append(line)) + Some( + lines + .iter_mut() + .map(|line| lexer::lex(line)) + .map(|lex| parser::parse(lex)) + .filter(|line| line.valid()) + .fold(TimeFile::default(), |file, line| file.append(line)), + ) } diff --git a/apps/cassiopeia/src/lib.rs b/apps/cassiopeia/src/lib.rs index d7b602bd1c50..1ea50fb8bc17 100644 --- a/apps/cassiopeia/src/lib.rs +++ b/apps/cassiopeia/src/lib.rs @@ -1,8 +1,129 @@ //! 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 format; pub mod meta; +mod time; pub use data::{Session, TimeFile}; pub use format::load_file; + +/// A state handler for all cass interactions +/// +/// This could be a stateless API, but I like being able to refer to +/// fields that need to be saved for later here. This API wraps +/// around [`TimeFile`](crate::TimeFile), so that you don't have to! ✨ +pub struct Cassiopeia { + path: String, + tf: TimeFile, +} + +impl Cassiopeia { + /// Load a cass file from disk, parsing it into a [`TimeFile`](crate::TimeFile) + pub fn load(path: &str) -> Option { + let path = path.to_owned(); + load_file(path.as_str()).map(|tf| Self { path, tf }) + } + + /// Store the modified time file back to disk + pub fn store(&self) -> Option<()> { + Some(()) + } + + /// Start a new work session (with optional 15 minute rounding) + pub fn start(&mut self, round: bool) -> Option<()> { + self.tf.start(round) + } + + /// Stop the existing work session (with optional 15 minute rounding) + pub fn stop(&mut self, round: bool) -> Option<()> { + Some(()) + } + + /// Add an invoice block to the time file + pub fn invoice<'slf>(&'slf mut self) -> Invoicer<'slf> { + Invoicer::new(self) + } +} + +/// 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(); +/// ``` +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(), + } + } + + /// S + 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(self) -> Option<()> { + if self.generate { + eprintln!("Integration with invoice(1) is currently not implemented. Sorry :()"); + return None; + } + + None + } +} diff --git a/apps/cassiopeia/src/meta.rs b/apps/cassiopeia/src/meta.rs index 835444f9bdc5..6d74d95bfe2a 100644 --- a/apps/cassiopeia/src/meta.rs +++ b/apps/cassiopeia/src/meta.rs @@ -19,7 +19,9 @@ pub const ARG_ROUNDING: &'static str = "CASS_ROUNDING"; pub const ARG_ROUNDING_ABOUT: &'static str = "Disable the (default) 15 minute rounding period"; pub const CMD_INVOICE: &'static str = "invoice"; -pub const CMD_INVOICE_ABOUT: &'static str = "Create an invoice"; +pub const CMD_INVOICE_ABOUT: &'static str = "Create an invoice. You get to choose between simply adding a \ +statement to your time file, or generating .yml configuration to build an invoice generator from. See invoice(1) \ +for more detail!"; pub const ARG_CLIENT: &'static str = "CLIENT"; pub const ARG_CLIENT_ABOUT: &'static str = @@ -32,3 +34,7 @@ pub const ARG_PROJECT_ABOUT: &'static str = pub const ARG_GEN_YAML: &'static str = "GEN_YAML"; pub const ARG_GEN_YAML_ABOUT: &'static str = "Specify whether to generate a .yml invoice configuration"; + +pub const ARG_CLIENT_DB: &'static str = "CLIENT_DB"; +pub const ARG_CLIENT_DB_ABOUT: &'static str = + "Provide your client database file (.yml format) used by invoice(1)"; -- cgit v1.2.3