/// simple typed helper enums mod simple_enums; mod transaction; fn main() { let mut args: Vec<_> = std::env::args().skip(1).collect(); if args.is_empty() { return; } use chrono::Datelike; use encoding::types::Encoding; use prettytable::{cell, format, row, Table}; use std::{ collections::{BTreeMap, HashSet}, io::Write, }; use itertools::Itertools; let mut highlight = HashSet::new(); if args[0] == "--highlight" { args.remove(0); let eo_selst = args.iter().take_while(|i| *i != ";").count(); highlight = args .drain(..std::cmp::min(eo_selst + 1, args.len())) .collect(); highlight.remove(";"); } let mut dat: Vec<_> = args.into_iter().flat_map(|i| { let fh = readfilez::read_from_file(std::fs::File::open(i)).expect("unable to open file"); let tmp = encoding::all::ISO_8859_1 .decode(&*fh, encoding::types::DecoderTrap::Replace) .expect("got invalid latin-1 data"); std::mem::drop(fh); let mut rdr = csv::ReaderBuilder::new() .delimiter(b';') .flexible(true) .has_headers(false) .from_reader(tmp.as_bytes()); let mut recsit = rdr.records().skip(8); assert_eq!( recsit.next().unwrap().unwrap(), vec![ "Buchungstag", "Valuta", "Auftraggeber/Zahlungsempfänger", "Empfänger/Zahlungspflichtiger", "Konto-Nr.", "IBAN", "BLZ", "BIC", "Vorgang/Verwendungszweck", "Kundenreferenz", "Währung", "Umsatz", " " ] ); let mut idat = Vec::new(); for result in recsit { let record = result.expect("got invalid line"); let record_bak = record.clone(); let pres: Result = std::convert::TryInto::try_into(record); match pres { Ok(tl) => idat.push(tl), Err(transaction::ParseError::Finalizer) => break, Err(x) => panic!("got error '{}' @ {:?}", x, record_bak), } } idat }).collect(); let mut stdout = term::stdout().unwrap(); dat.sort(); let oldlen = dat.len(); let mut accu = BTreeMap::< (i32, bool, String), (usize, transaction::TransactionValue, String, usize), >::new(); let mut i_skipped = 0usize; let mut newlen = 0usize; for i in dat.into_iter().dedup() { newlen += 1; if i.direction != simple_enums::TransactionDirection::Haben || i.waehrung != simple_enums::Waehrung::EUR || i.p_other.is_empty() || i.p_other.find(" ZINS BIS ").is_some() { i_skipped += 1; continue; } let mut ent = accu .entry(( i.d_buchungs.year(), i.d_buchungs.month() > 6, i.p_other.clone(), )) .or_default(); ent.0 += 1; ent.1 += i.umsatz; } writeln!(&mut stdout, "skipped {} duplicates and {} misc entries", oldlen - newlen, i_skipped).unwrap(); if accu.is_empty() { return; } let mut pdsp = 0; for i in accu.values_mut() { i.2 = i.1.to_string(); i.3 = i.2.find('.').map(|x| i.2.len() - x).unwrap_or(0); pdsp = std::cmp::max(pdsp, i.3); } let mut table = Table::new(); table.set_format( format::FormatBuilder::new() .column_separator('│') .borders('│') .separator( format::LinePosition::Top, format::LineSeparator::new('─', '┬', '┌', '┐'), ) .separator( format::LinePosition::Title, format::LineSeparator::new('─', '┼', '├', '┤'), ) .separator( format::LinePosition::Bottom, format::LineSeparator::new('─', '┴', '└', '┘'), ) .padding(1, 1) .build(), ); table.set_titles(row!["Jahr", "Einzahler", "Zahlungen", "Summe"]); for (k, v) in accu.iter_mut() { let yearuhj = format!("{} {}", k.0, if k.1 { "II" } else { "I" }); v.2 += &std::iter::repeat(' ').take(pdsp - v.3).collect::(); table.add_row(if highlight.contains(&*k.2) { row![yearuhj, FYBdb-> k.2, r-> v.0.to_string(), Fgbr-> v.2] } else { row![yearuhj, k.2, r-> v.0.to_string(), r-> v.2] }); } table .print_term(&mut *stdout) .expect("unable to print table"); }