From 60cc74d344a97b368e7b31b37d3c29aa49fc1602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustav=20S=C3=B6rn=C3=A4s?= Date: Thu, 29 Jul 2021 13:23:28 +0200 Subject: model -> transaction/store --- cli/src/main.rs | 9 ++-- cli/src/model.rs | 123 ------------------------------------------------- cli/src/store.rs | 62 +++++++++++++++++++++++++ cli/src/transaction.rs | 64 +++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 127 deletions(-) delete mode 100644 cli/src/model.rs create mode 100644 cli/src/store.rs create mode 100644 cli/src/transaction.rs (limited to 'cli/src') diff --git a/cli/src/main.rs b/cli/src/main.rs index 18d422a..10fa02c 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -4,7 +4,8 @@ use std::path::PathBuf; use std::str::FromStr; use structopt::StructOpt; -mod model; +mod store; +mod transaction; //TODO relative ("yesterday", "-2d", etc) fn parse_date(s: &str) -> Result { @@ -15,7 +16,7 @@ fn parse_date(s: &str) -> Result { #[derive(StructOpt)] enum Command { Insert { - kind: model::TransactionKind, + kind: transaction::TransactionKind, #[structopt(long)] account: String, @@ -61,7 +62,7 @@ struct Mn { } fn main() { - let mut store = model::Store::open(PathBuf::from("store")).unwrap(); + let mut store = store::Store::open(PathBuf::from("store")).unwrap(); let args = Mn::from_args(); eprintln!("{:?}", args); match args.command { @@ -73,7 +74,7 @@ fn main() { description, date, } => { - let transaction = model::Transaction { + let transaction = transaction::Transaction { kind, to: account, from: "Default".to_string(), diff --git a/cli/src/model.rs b/cli/src/model.rs deleted file mode 100644 index f4b9910..0000000 --- a/cli/src/model.rs +++ /dev/null @@ -1,123 +0,0 @@ -use chrono::naive::NaiveDate; -use rust_decimal::Decimal; -use serde::{Deserialize, Serialize}; -use std::convert::AsRef; -use std::fs; -use std::hash::{Hash, Hasher}; -use std::path::{Path, PathBuf}; -use structopt::StructOpt; -use twox_hash::XxHash64; - -type Account = String; -type Category = String; - -#[derive(Debug)] -#[derive(Hash)] -#[derive(Deserialize, Serialize)] -#[derive(StructOpt)] -pub enum TransactionKind { - Expense, - Income, - //TODO Transfer, -} - -impl std::str::FromStr for TransactionKind { - type Err = String; - - fn from_str(s: &str) -> Result { - match s { - "expense" => Ok(TransactionKind::Expense), - "income" => Ok(TransactionKind::Income), - _ => Err(format!("Unknown transaction kind: {:?}", s)), - } - } -} - -#[derive(Debug)] -#[derive(Hash)] -#[derive(Deserialize, Serialize)] -pub struct Transaction { - pub description: String, - pub date: NaiveDate, - pub category: Category, - pub amount: Decimal, - pub kind: TransactionKind, - pub from: Account, - pub to: Account, -} - -#[derive(Debug)] -pub struct Store { - root: PathBuf, - transactions: Vec, - new_transactions: Vec, -} - -impl Store { - //TODO Result - pub fn open(root: PathBuf) -> Option { - Some(Self { - transactions: Self::open_dir(&root)?, - new_transactions: Vec::new(), - root, - }) - } - - //TODO check if hash matches - //TODO Result - //TODO overkill? maybe we can use subfolders later on - fn open_dir(dir: &Path) -> Option> { - let mut res = Vec::new(); - for entry in std::fs::read_dir(dir).ok()? { - let entry = entry.ok()?; - if entry.file_type().ok()?.is_dir() { - let mut transactions = Self::open_dir(&entry.path())?; - res.append(&mut transactions); - } else { - res.push(Transaction::open(&entry.path())?); - } - } - Some(res) - } - - pub fn push(&mut self, transaction: Transaction) { - self.new_transactions.push(transaction); - } - - pub fn write(&self) -> std::io::Result<()> { - for transaction in &self.new_transactions { - let mut path = self.root.clone(); - path.push(format!("{}", transaction.id())); - transaction.write(&path)?; - } - Ok(()) - } - - pub fn categories(&self) -> Vec { - let mut categories: Vec<_> = self - .transactions - .iter() - .map(|t| t.category.clone()) - .collect(); - categories.sort(); - categories.dedup(); - categories - } -} - -impl Transaction { - fn write>(&self, p: &P) -> std::io::Result<()> { - fs::write(p, serde_json::to_string_pretty(self).unwrap()) //TODO control pretty or not - } - - //TODO Result - fn open>(p: &P) -> Option { - fs::read_to_string(p).ok().as_ref().and_then(|s| serde_json::from_str(s).ok()) - } - - pub fn id(&self) -> u64 { - let mut h = XxHash64::default(); - self.hash(&mut h); - h.finish() - } -} diff --git a/cli/src/store.rs b/cli/src/store.rs new file mode 100644 index 0000000..2bba9c8 --- /dev/null +++ b/cli/src/store.rs @@ -0,0 +1,62 @@ +use std::path::{Path, PathBuf}; + +use crate::transaction::{Category, Transaction}; + +#[derive(Debug)] +pub struct Store { + root: PathBuf, + transactions: Vec, + new_transactions: Vec, +} + +impl Store { + //TODO Result + pub fn open(root: PathBuf) -> Option { + Some(Self { + transactions: Self::open_dir(&root)?, + new_transactions: Vec::new(), + root, + }) + } + + //TODO check if hash matches + //TODO Result + //TODO overkill? maybe we can use subfolders later on + fn open_dir(dir: &Path) -> Option> { + let mut res = Vec::new(); + for entry in std::fs::read_dir(dir).ok()? { + let entry = entry.ok()?; + if entry.file_type().ok()?.is_dir() { + let mut transactions = Self::open_dir(&entry.path())?; + res.append(&mut transactions); + } else { + res.push(Transaction::open(&entry.path())?); + } + } + Some(res) + } + + pub fn push(&mut self, transaction: Transaction) { + self.new_transactions.push(transaction); + } + + pub fn write(&self) -> std::io::Result<()> { + for transaction in &self.new_transactions { + let mut path = self.root.clone(); + path.push(format!("{}", transaction.id())); + transaction.write(&path)?; + } + Ok(()) + } + + pub fn categories(&self) -> Vec { + let mut categories: Vec<_> = self + .transactions + .iter() + .map(|t| t.category.clone()) + .collect(); + categories.sort(); + categories.dedup(); + categories + } +} diff --git a/cli/src/transaction.rs b/cli/src/transaction.rs new file mode 100644 index 0000000..117edfb --- /dev/null +++ b/cli/src/transaction.rs @@ -0,0 +1,64 @@ +use chrono::naive::NaiveDate; +use rust_decimal::Decimal; +use serde::{Deserialize, Serialize}; +use std::convert::AsRef; +use std::fs; +use std::hash::{Hash, Hasher}; +use std::path::Path; +use structopt::StructOpt; +use twox_hash::XxHash64; + +pub(crate) type Account = String; +pub(crate) type Category = String; + +#[derive(Debug)] +#[derive(Hash)] +#[derive(Deserialize, Serialize)] +#[derive(StructOpt)] +pub enum TransactionKind { + Expense, + Income, + //TODO Transfer, +} + +impl std::str::FromStr for TransactionKind { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "expense" => Ok(TransactionKind::Expense), + "income" => Ok(TransactionKind::Income), + _ => Err(format!("Unknown transaction kind: {:?}", s)), + } + } +} + +#[derive(Debug)] +#[derive(Hash)] +#[derive(Deserialize, Serialize)] +pub struct Transaction { + pub description: String, + pub date: NaiveDate, + pub category: Category, + pub amount: Decimal, + pub kind: TransactionKind, + pub from: Account, + pub to: Account, +} + +impl Transaction { + pub(crate) fn write>(&self, p: &P) -> std::io::Result<()> { + fs::write(p, serde_json::to_string_pretty(self).unwrap()) //TODO control pretty or not + } + + //TODO Result + pub(crate) fn open>(p: &P) -> Option { + fs::read_to_string(p).ok().as_ref().and_then(|s| serde_json::from_str(s).ok()) + } + + pub fn id(&self) -> u64 { + let mut h = XxHash64::default(); + self.hash(&mut h); + h.finish() + } +} -- cgit v1.2.1