psdn-tacsvs/src/main.rs
2022-10-14 12:15:13 +02:00

189 lines
5.6 KiB
Rust

mod codec;
/// 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,
itertools::Itertools,
prettytable::{format, row, Table},
rayon::prelude::*,
std::{
collections::{BTreeMap, HashSet},
io::Write,
},
};
let mut highlight = HashSet::new();
let hl_only = args[0] == "--highlight-only";
if args[0] == "--highlight" || hl_only {
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 i_skipped = std::sync::atomic::AtomicUsize::new(0);
let mut dat: Vec<_> = args
.into_par_iter()
.flat_map(|i| {
let mut rdr = csv::ReaderBuilder::new()
.delimiter(b';')
.flexible(true)
.has_headers(false)
.from_reader(codec::Latin12Utf8Reader::new(
std::fs::File::open(i).expect("unable to open file"),
));
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();
let mut iisk = 0usize;
for result in recsit {
let record = result.expect("got invalid line");
let record_bak = record.clone();
let pres: Result<transaction::TransactionLine, _> =
std::convert::TryInto::try_into(record);
match pres {
Ok(tl) => {
if tl.direction == simple_enums::TransactionDirection::Haben
&& tl.waehrung == simple_enums::Waehrung::Eur
&& !tl.p_other.is_empty()
&& !tl.p_other.contains(" ZINS BIS ")
{
idat.push(tl)
} else {
iisk += 1;
}
}
Err(transaction::ParseError::Finalizer) => break,
Err(x) => panic!("got error '{}' @ {:?}", x, record_bak),
}
}
i_skipped.fetch_add(iisk, std::sync::atomic::Ordering::SeqCst);
idat
})
.collect();
dat.par_sort();
let oldlen = dat.len();
let mut newlen = 0usize;
let mut stdout = term::stdout().unwrap();
let mut accu = BTreeMap::<
(i32, bool, String),
(usize, transaction::TransactionValue, String, usize),
>::new();
for i in dat.into_iter().dedup() {
newlen += 1;
let mut ent = accu
.entry((i.d_buchungs.year(), i.d_buchungs.month() > 6, i.p_other))
.or_default();
ent.0 += 1;
ent.1 += i.umsatz;
}
writeln!(
&mut stdout,
"skipped {} duplicates and {} misc entries",
oldlen - newlen,
i_skipped.into_inner(),
)
.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"]);
let mut prev_yuhj = Option::<String>::None;
for (k, v) in accu.iter_mut() {
let is_hl = highlight.contains(&*k.2);
if !hl_only || is_hl {
let mut yearuhj = format!("{} {}I", k.0, if k.1 { "I" } else { "" });
let is_ny = prev_yuhj.as_ref().map(|i| i != &yearuhj);
v.2 += &" ".repeat(pdsp - v.3);
if is_ny.unwrap_or(false) {
table.add_row(row!["", "", "", ""]);
}
if is_ny.unwrap_or(true) {
prev_yuhj = Some(yearuhj.clone());
} else {
yearuhj.clear();
}
table.add_row(if !hl_only && is_hl {
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");
}