koffice: implement libko configuration store loading

wip/yesman
Katharina Fey 3 years ago
parent 7a81c4d375
commit 76799d3bd6
Signed by: kookie
GPG Key ID: 90734A9E619C8A6C
  1. 9
      apps/koffice/libko/Cargo.toml
  2. 64
      apps/koffice/libko/src/invoice.rs
  3. 4
      apps/koffice/libko/src/lib.rs
  4. 108
      apps/koffice/libko/src/store.rs

@ -5,8 +5,13 @@ authors = ["Katharina Fey <kookie@spacekookie.de>"]
edition = "2018"
[dependencies]
chrono = { version = "0.4", features = [ "serde" ] }
serde = { version = "1.0", features = [ "derive" ] }
serde_yaml = "*"
logos = "0.11"
chrono = { version = "0.4", features = [ "serde" ] }
xdg = "2.2.0"
logos = "0.11"
colored = "2.0"
env_logger = "*"
log = "*"

@ -1,6 +1,7 @@
use chrono::NaiveDate;
use crate::Io;
use chrono::{NaiveDate, Utc};
use serde::{Deserialize, Serialize};
use std::string::ToString;
use std::{fs::File, io::Read, path::Path, string::ToString};
/// A specification to build invoice IDs with
#[derive(Serialize, Deserialize)]
@ -8,6 +9,56 @@ pub enum InvoiceId {
YearMonthId(u16, u8, usize),
}
impl InvoiceId {
pub(crate) fn date() -> Self {
let now = Utc::now().naive_local().date();
let year = now.format("%Y").to_string();
let month = now.format("%m").to_string();
Self::YearMonthId(
str::parse(&year).unwrap_or_else(|_| {
fatal!(
"Current year doesn't fit into u16. WHEN ARE YOU USING THIS CODE???????? (have you abolished capitalism yet? uwu)"
)
}),
str::parse(&month)
.unwrap_or_else(|_| fatal!("Invalid month value (doesn't fit in u8)")),
0,
)
}
/// Utility to find the next invoice ID in a sequence
///
/// Start with an InvoiceID that only has the correct date
/// (`date()`), then call this function for each existing invoice
/// in your collection. If the current ID is lower or equal than
/// the one given, take the given ID and increment it by one.
///
/// Once this has been done for all invoices in the collection,
/// you are guaranteed to have the latest invoice ID.
pub(crate) fn find_next(mut self, i: &Invoice) -> Self {
if self.numeric() <= i.id.numeric() {
self.update_numeric(i.id.numeric() + 1);
}
self
}
fn update_numeric(&mut self, id: usize) {
match self {
Self::YearMonthId(_, _, ref mut _id) => {
*_id = id;
}
}
}
/// A valid invoice ID needs to have a numeric element
pub(crate) fn numeric(&self) -> usize {
match self {
Self::YearMonthId(_, _, id) => *id,
}
}
}
impl ToString for InvoiceId {
fn to_string(&self) -> String {
match self {
@ -28,6 +79,15 @@ pub struct Invoice {
vat: u8,
}
impl Invoice {
pub fn load(path: &Path) -> Option<Self> {
let mut buf = String::new();
let mut f = File::open(path).ok()?;
f.read_to_string(&mut buf).ok()?;
Some(Self::from_yaml(buf))
}
}
#[test]
fn invoice_id_fmt() {
let inv_id = InvoiceId::YearMonthId(2020, 06, 0055);

@ -1,4 +1,8 @@
//! A library that provides basic building blocks of k-office tools
#![allow(warnings)]
#[macro_use] extern crate log;
#[macro_use] pub mod log_util;
pub mod cass;

@ -1,11 +1,11 @@
use crate::{
cass::{Cassiopeia, TimeFile},
Address, Client, Io,
Address, Client, Invoice, InvoiceId, Io,
};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::{
fs::File,
fs::{self, File, OpenOptions as Oo},
io::{Read, Write},
path::PathBuf,
};
@ -31,25 +31,27 @@ pub struct Config {
pub invoice_dir: PathBuf,
}
impl Config {
fn load(path: PathBuf) -> Self {
let mut buf = String::new();
let mut f = File::open(path).unwrap();
f.read_to_string(&mut buf).unwrap();
Self::from_yaml(buf)
}
fn store(&self, path: PathBuf) {
let mut f = Oo::new().truncate(true).write(true).open(path).unwrap();
let buf = self.to_yaml();
f.write_all(buf.as_bytes()).unwrap();
}
}
impl Meta {
pub fn new(dir: BaseDirs) -> Self {
// Get the path to the configuration, and make sure a default
// configuration is created if none exists yet.
let path = dir.find_config_file("config.yml").unwrap_or_else(|| {
let path = dir.place_config_file("config.yml").unwrap();
let mut cfg = File::create(path.clone()).unwrap();
let buf = "revisioning: true
invoicedir: $HOME/.local/k-office/";
cfg.write_all(buf.as_bytes()).unwrap();
path
});
let mut cfg = File::open(path).unwrap();
let mut buf = String::new();
cfg.read_to_string(&mut buf).unwrap();
let yml = Config::from_yaml(buf);
let path = config_path(&dir);
let yml = Config::load(path);
Self {
dir,
@ -63,10 +65,19 @@ invoicedir: $HOME/.local/k-office/";
}
pub fn load_timefile(&mut self, path: &str) {
let timefile = Cassiopeia::load(path)
.expect("Timefile not found")
.timefile();
self.timefile = Some(timefile);
self.timefile = Some(Cassiopeia::load(path)
.map(|s| s.timefile())
.unwrap_or_else(|e| fatal!("Failed reading timefile: {}", e.to_string())));
}
pub fn load_config(&self) -> Config {
let path = config_path(&self.dir);
Config::load(path)
}
pub fn store_config(&self, cfg: Config) {
let path = config_path(&self.dir);
cfg.store(path);
}
pub fn client_mut(&mut self, name: &str) -> Option<&mut Client> {
@ -83,12 +94,65 @@ invoicedir: $HOME/.local/k-office/";
},
);
}
pub fn new_invoice_id(&self) -> InvoiceId {
let files = match fs::read_dir(self.invoice_dir.as_path()) {
Ok(f) => f,
Err(_) => {
let path = self
.dir
.create_config_directory("invoices")
.unwrap_or_else(|_| fatal!("Failed to create invoice directory"));
fs::read_dir(path).unwrap()
}
};
files.into_iter().fold(InvoiceId::date(), |inv, f| {
let f = f.unwrap();
let i = Invoice::load(&f.path()).unwrap();
inv.find_next(&i)
})
}
/// Update a configuration value and write it back to disk
pub fn update_config(&self, key: String, value: Option<String>) {
let mut cfg = self.load_config();
match (key.as_str(), value) {
("invoice_dir", Some(val)) => cfg.invoice_dir = PathBuf::new().join(val),
("invoice_dir", None) => fatal!("Can not unset 'invoice_dir' configuration key"),
(key, _) => fatal!("Unrecognised configuration key '{}'", key),
}
self.store_config(cfg);
}
}
/// Initialise a k-office application state
pub fn initialise() -> Meta {
let dir = BaseDirs::with_prefix("k-koffice").unwrap();
dir.create_config_directory("")
.expect("Couldn't create config directory");
.unwrap_or_else(|_| fatal!("Couldn't create config directory"));
Meta::new(dir)
}
fn config_path(dir: &BaseDirs) -> PathBuf {
dir.find_config_file("config.yml").unwrap_or_else(|| {
let path = dir.place_config_file("config.yml").unwrap();
let mut cfg = File::create(path.clone()).unwrap();
let invoice_dir: String = dir
.get_config_home()
.join("invoices")
.to_str()
.map(Into::into)
.unwrap_or_else(|| fatal!("XDG_CONFIG_HOME contained non UTF-8 characters :("));
let buf = format!(
"revisioning: true
invoice_dir: {}",
invoice_dir
);
cfg.write_all(buf.as_bytes()).unwrap();
path
})
}

Loading…
Cancel
Save