diff options
Diffstat (limited to 'src/qif.rs')
-rw-r--r-- | src/qif.rs | 131 |
1 files changed, 92 insertions, 39 deletions
@@ -1,18 +1,19 @@ -use std::fmt; +use lazy_static::lazy_static; use regex::Regex; -use std::result::Result; use std::error::Error; +use std::fmt; +use std::result::Result; pub struct QifFile { header: String, - entries: Vec<QifEntry> + entries: Vec<QifEntry>, } impl QifFile { pub fn new(header: String) -> QifFile { QifFile { - header: header, - entries: Vec::new() + header, + entries: Vec::new(), } } @@ -40,7 +41,7 @@ impl fmt::Display for QifFile { pub struct QifEntry { date: String, amount: String, - description: String + description: String, } const DATE_PREFIX: &str = "D"; @@ -56,7 +57,7 @@ impl QifEntry { (Some(date), Some(amount), Some(description)) => Ok(QifEntry { date: date.chars().skip(1).collect(), amount: amount.chars().skip(1).collect(), - description: description.chars().skip(1).collect() + description: description.chars().skip(1).collect(), }), (None, _, _) => Err(QifParsingError::MissingDate), (_, None, _) => Err(QifParsingError::MissingAmount), @@ -65,65 +66,117 @@ impl QifEntry { } pub fn is_empty(&self) -> bool { - self.amount == String::from("0") + &self.amount == "0" || &self.amount == "0.00" } pub fn clean_description(&self) -> String { - let replaced_date = replace_date(&self.description); - replace_common(&replaced_date) + let mut new_description = self.description.clone(); + for algorithm in [ + remove_date, + remove_card_number, + replace_common, + remove_payment_provider, + ] { + new_description = algorithm(&new_description); + } + new_description } } impl fmt::Display for QifEntry { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}{}\n{}{}\n{}{}", - DATE_PREFIX, self.date, - AMOUNT_PREFIX, self.amount, - DESCRIPTION_PREFIX, self.clean_description() + write!( + f, + "{}{}\n{}{}\n{}{}", + DATE_PREFIX, + self.date, + AMOUNT_PREFIX, + self.amount, + DESCRIPTION_PREFIX, + self.clean_description() ) } } -fn replace_date(text: &str) -> String { +fn remove_date(text: &str) -> String { lazy_static! { - static ref DATE_REGEX: Regex = Regex::new(r"(?i)(\d{2} )?(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)( \d{4})?").unwrap(); + static ref DATE_REGEX: Regex = + Regex::new(r"(?i)\d{2} (JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)( \d{4})?") + .unwrap(); } DATE_REGEX.replace_all(text, "").trim().to_string() } +fn remove_card_number(text: &str) -> String { + lazy_static! { + static ref CARD_NUM_REGEX: Regex = Regex::new(r"(\d{6}\*+\d{4}|\d{16})").unwrap(); + } + CARD_NUM_REGEX.replace_all(text, "").trim().to_string() +} + +fn remove_payment_provider(text: &str) -> String { + lazy_static! { + static ref PURCH_REGEX: Regex = + Regex::new(r"(?i)^(purch( payfast\*)?|pp +\*|c\*|yoco +\*|ikh\*)").unwrap(); + } + PURCH_REGEX.replace_all(text, "").trim().to_string() +} + fn replace_common(text: &str) -> String { lazy_static! { static ref COMMON_NAMES: Vec<(Regex, &'static str)> = vec!( - (Regex::new(r"(?i)(pick n pay|pnp)").unwrap(), "Pick n Pay"), - (Regex::new(r"(?i)checkers").unwrap(), "Checkers"), - (Regex::new(r"(?i)WOOLWORTHS").unwrap(), "Woolworths"), - (Regex::new(r"(?i)clicks").unwrap(), "Clicks"), - (Regex::new(r"(?i)spar").unwrap(), "Spar"), - (Regex::new(r"(?i)Crazy store").unwrap(), "Crazy Store"), - (Regex::new(r"^PNA").unwrap(), "PNA"), - (Regex::new(r"(?i)sahl").unwrap(), "SA Home Loans"), - (Regex::new(r"(?i)gautrain").unwrap(), "Gautrain"), - (Regex::new(r"(?i)BANK YOUR CHANGE DEBI").unwrap(), "TO SAVINGS POCKET"), - (Regex::new(r"(?i)AFRIHOST").unwrap(), "Afrihost"), - (Regex::new(r"(?i)DIALDIRECT").unwrap(), "Dialdirect"), - (Regex::new(r"(?i)STEERS").unwrap(), "Steers"), - (Regex::new(r"(?i)CELL C").unwrap(), "Cell C"), - (Regex::new(r"(?i)ELECTRICITY").unwrap(), "Electricity"), - (Regex::new(r"(?i)(COUNTRY VIEW|STAR STOP|Shell|Sasol)").unwrap(), "Petrol"), - (Regex::new(r"(?i)kung ?-?fu").unwrap(), "Kungfu Kitchen"), - ); + (r"(?i)(pick n pay|pnp)", "Pick n Pay"), + (r"(?i)checkers", "Checkers"), + (r"(?i)WOOLWORTHS", "Woolworths"), + (r"(?i)clicks", "Clicks"), + (r"(?i)dischem", "Dischem"), + (r"(?i)spar", "Spar"), + (r"(?i)(disc memb|disc prem)", "Discovery medical aid"), + (r"(?i)10XRA", "10X Retirement Annuity"), + (r"(?i)NEDABF/MFC", "Nedbank MFC"), + (r"(?i)SARSEFLNG", "SARS Efiling"), + (r"(?i)Crazy store", "Crazy Store"), + (r"^PNA", "PNA"), + (r"^BWH", "Builders Warehouse"), + (r"^MCD ", "McDonalds"), + (r"^MRP ", "Mr Price"), + (r"NakedIn", "Naked Insurance"), + (r"(?i)sahl", "SA Home Loans"), + (r"(?i)gautrain", "Gautrain"), + (r"(?i)BYC DEBIT", "TO SAVINGS POCKET"), + (r"(?i)AFRIHOST", "Afrihost"), + (r"(?i)DIALDIRECT", "Dialdirect"), + (r"(?i)STEERS", "Steers"), + (r"(?i)CELL C", "Cell C"), + (r"(?i)ELECTRICITY", "Electricity"), + (r"(?i)(COUNTRY VIEW|STAR STOP|Shell|Sasol|Engen)", "Petrol"), + (r"(?i)kung ?-?fu", "Kungfu Kitchen"), + (r"(?i)^atm cash", "Cash"), + (r"(?i)DRS CL & ME LA", "Medipark 24") + ) + .into_iter() + .map(|(from, to)| (Regex::new(from).unwrap(), to)) + .collect(); } - COMMON_NAMES.iter().fold( - text, |acc, next| if next.0.is_match(acc) {next.1} else {acc} - ).to_string() + COMMON_NAMES + .iter() + .filter_map(|(rule, replacement)| { + if rule.is_match(text) { + Some(replacement.to_string()) + } else { + None + } + }) + .next() + .unwrap_or_else(|| text.to_owned()) + .to_string() } - #[derive(Debug)] pub enum QifParsingError { MissingDate, MissingAmount, - MissingDescription + MissingDescription, } impl fmt::Display for QifParsingError { |