aboutsummaryrefslogtreecommitdiff
path: root/apps/cassiopeia
diff options
context:
space:
mode:
authorMx Kookie <kookie@spacekookie.de>2020-12-12 11:24:43 +0000
committerMx Kookie <kookie@spacekookie.de>2020-12-21 05:19:49 +0100
commit4c97f3208a0ba185264a169e01d0b0d922266ea6 (patch)
treefe35d9da3e60f1169eb97242b1948ccb5f9341cd /apps/cassiopeia
parentd346c9f2f30041623e51b120e767b1ce02083e97 (diff)
cassiopeia: adding API outline
Diffstat (limited to 'apps/cassiopeia')
-rw-r--r--apps/cassiopeia/src/bin/cass.rs9
-rw-r--r--apps/cassiopeia/src/data.rs9
-rw-r--r--apps/cassiopeia/src/format/mod.rs22
-rw-r--r--apps/cassiopeia/src/lib.rs121
-rw-r--r--apps/cassiopeia/src/meta.rs8
5 files changed, 157 insertions, 12 deletions
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<TimeFile> {
+ 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<String> = 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<Self> {
+ 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)";