Compare commits
10 commits
9cc7cbc7b8
...
b1af3da189
Author | SHA1 | Date | |
---|---|---|---|
|
b1af3da189 | ||
|
6ec3af5999 | ||
|
954df2f86a | ||
|
5684348a5d | ||
|
3a840afda4 | ||
|
13e69f5b9c | ||
|
c3fd68249d | ||
|
cbd0ab82bd | ||
|
35541de631 | ||
|
83e9219049 |
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1,5 @@
|
|||
result
|
||||
result-*
|
||||
/target
|
||||
/flamegraph.svg
|
||||
/perf.data*
|
||||
|
|
844
Cargo.lock
generated
844
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
13
Cargo.toml
13
Cargo.toml
|
@ -7,15 +7,12 @@ edition = "2018"
|
|||
[dependencies]
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
csv = "1.1"
|
||||
encoding = "0.2"
|
||||
fixed = { version = "0.5", features = ["serde", "std"] }
|
||||
itertools = "0.8"
|
||||
prettytable = { version = "0.8", package = "prettytable-rs" }
|
||||
rayon = "1.3"
|
||||
readfilez = "0.2"
|
||||
ron = "0.5"
|
||||
fixed = { version = "1.20", features = ["serde", "std"] }
|
||||
itertools = "0.10"
|
||||
prettytable = { version = "0.9", package = "prettytable-rs" }
|
||||
rayon = "1.6"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
term = "0.5"
|
||||
term = "0.7"
|
||||
thiserror = "1.0"
|
||||
|
||||
[profile.release]
|
||||
|
|
43
flake.lock
Normal file
43
flake.lock
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1671884884,
|
||||
"narHash": "sha256-JHX4CdMwDdYFeHzx2gKTe66Dx6vE8WXtND0twqann1g=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "ff76349c4f6ef483ecfd2591ce424c741925b111",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "master",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"yz-flake-utils": "yz-flake-utils"
|
||||
}
|
||||
},
|
||||
"yz-flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1660779307,
|
||||
"narHash": "sha256-WiuDu1qAj7I1GY7FlNK5cMaCa94CKgdv0cumUXCavww=",
|
||||
"owner": "YZITE",
|
||||
"repo": "flake-utils",
|
||||
"rev": "8fecd51bdf7138bfb6b7ac3340d0f65e2680556f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "YZITE",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
14
flake.nix
Normal file
14
flake.nix
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
description = "PSD Bank N. CSV summarizer";
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/master";
|
||||
yz-flake-utils.url = "github:YZITE/flake-utils";
|
||||
};
|
||||
outputs = { nixpkgs, yz-flake-utils, ... }:
|
||||
yz-flake-utils.lib.mkFlakeFromProg {
|
||||
contentAddressedByDefault = false;
|
||||
prevpkgs = nixpkgs;
|
||||
progname = "psdn-tacsvs";
|
||||
drvBuilder = final: prev: (import ./Cargo.nix { pkgs = final; }).rootCrate.build;
|
||||
};
|
||||
}
|
76
src/codec.rs
Normal file
76
src/codec.rs
Normal file
|
@ -0,0 +1,76 @@
|
|||
use std::io::{self, Read};
|
||||
|
||||
pub struct Latin12Utf8Reader<X> {
|
||||
inner: std::io::Bytes<X>,
|
||||
buf: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<X: Read> Latin12Utf8Reader<X> {
|
||||
pub fn new(x: X) -> Self {
|
||||
Self {
|
||||
inner: x.bytes(),
|
||||
buf: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn move_to_dest(&mut self, buf: &mut [u8]) -> usize {
|
||||
let ret = std::cmp::min(buf.len(), self.buf.len());
|
||||
debug_assert!(ret > 0);
|
||||
buf[..ret].copy_from_slice(&self.buf[..ret]);
|
||||
let _ = self.buf.drain(..ret);
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl<X: Read> Read for Latin12Utf8Reader<X> {
|
||||
fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
|
||||
if buf.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
let mut ret = 0;
|
||||
if !self.buf.is_empty() {
|
||||
let ret2 = self.move_to_dest(buf);
|
||||
buf = &mut buf[ret2..];
|
||||
ret = ret2;
|
||||
}
|
||||
while !buf.is_empty() {
|
||||
if let Some(y) = self.inner.next() {
|
||||
match y {
|
||||
Ok(x) => {
|
||||
let x = if x <= 0x7f {
|
||||
x as char
|
||||
} else {
|
||||
std::char::from_u32(x as u32).ok_or_else(|| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
format!("got invalid input character: {:#02x}", x),
|
||||
)
|
||||
})?
|
||||
};
|
||||
let xl = x.len_utf8();
|
||||
if buf.len() >= xl {
|
||||
x.encode_utf8(buf);
|
||||
buf = &mut buf[xl..];
|
||||
} else {
|
||||
let mut tmp = [0; 4];
|
||||
x.encode_utf8(&mut tmp[..]);
|
||||
self.buf.extend(tmp.iter().take(xl).copied());
|
||||
ret += self.move_to_dest(buf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(x) if ret == 0 => {
|
||||
return Err(x);
|
||||
}
|
||||
Err(_) => {
|
||||
// ignore error
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
60
src/main.rs
60
src/main.rs
|
@ -1,3 +1,5 @@
|
|||
mod codec;
|
||||
|
||||
/// simple typed helper enums
|
||||
mod simple_enums;
|
||||
|
||||
|
@ -12,9 +14,8 @@ fn main() {
|
|||
|
||||
use {
|
||||
chrono::Datelike,
|
||||
encoding::types::Encoding,
|
||||
itertools::Itertools,
|
||||
prettytable::{cell, format, row, Table},
|
||||
prettytable::{format, row, Table},
|
||||
rayon::prelude::*,
|
||||
std::{
|
||||
collections::{BTreeMap, HashSet},
|
||||
|
@ -23,8 +24,9 @@ fn main() {
|
|||
};
|
||||
|
||||
let mut highlight = HashSet::new();
|
||||
let hl_only = args[0] == "--highlight-only";
|
||||
|
||||
if args[0] == "--highlight" {
|
||||
if args[0] == "--highlight" || hl_only {
|
||||
args.remove(0);
|
||||
let eo_selst = args.iter().take_while(|i| *i != ";").count();
|
||||
highlight = args
|
||||
|
@ -38,20 +40,13 @@ fn main() {
|
|||
let mut dat: Vec<_> = args
|
||||
.into_par_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());
|
||||
.from_reader(codec::Latin12Utf8Reader::new(
|
||||
std::fs::File::open(i).expect("unable to open file"),
|
||||
));
|
||||
|
||||
let mut recsit = rdr.records().skip(8);
|
||||
|
||||
|
@ -85,9 +80,9 @@ fn main() {
|
|||
match pres {
|
||||
Ok(tl) => {
|
||||
if tl.direction == simple_enums::TransactionDirection::Haben
|
||||
&& tl.waehrung == simple_enums::Waehrung::EUR
|
||||
&& tl.waehrung == simple_enums::Waehrung::Eur
|
||||
&& !tl.p_other.is_empty()
|
||||
&& tl.p_other.find(" ZINS BIS ").is_none()
|
||||
&& !tl.p_other.contains(" ZINS BIS ")
|
||||
{
|
||||
idat.push(tl)
|
||||
} else {
|
||||
|
@ -103,17 +98,16 @@ fn main() {
|
|||
})
|
||||
.collect();
|
||||
|
||||
let mut stdout = term::stdout().unwrap();
|
||||
dat.par_sort();
|
||||
|
||||
dat.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();
|
||||
|
||||
let mut newlen = 0usize;
|
||||
for i in dat.into_iter().dedup() {
|
||||
newlen += 1;
|
||||
let mut ent = accu
|
||||
|
@ -164,14 +158,28 @@ fn main() {
|
|||
);
|
||||
|
||||
table.set_titles(row!["Jahr", "Einzahler", "Zahlungen", "Summe"]);
|
||||
|
||||
let mut prev_yuhj = Option::<String>::None;
|
||||
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::<String>();
|
||||
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]
|
||||
});
|
||||
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
|
||||
|
|
|
@ -29,15 +29,18 @@ use {
|
|||
std::fmt,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
#[derive(Clone, Copy, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub enum Waehrung {
|
||||
EUR,
|
||||
USD,
|
||||
Eur,
|
||||
Usd,
|
||||
}
|
||||
|
||||
impl fmt::Display for Waehrung {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
write!(f, "{}", match self {
|
||||
Self::Eur => "EUR",
|
||||
Self::Usd => "USD",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,8 +49,8 @@ impl std::str::FromStr for Waehrung {
|
|||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match s {
|
||||
"EUR" => Waehrung::EUR,
|
||||
"USD" => Waehrung::USD,
|
||||
"EUR" => Self::Eur,
|
||||
"USD" => Self::Usd,
|
||||
_ => return Err(parser_error::Waehrung),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use thiserror::Error;
|
|||
|
||||
pub type TransactionValue = fixed::types::U53F11;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
#[derive(Clone, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub struct TransactionLine {
|
||||
pub d_buchungs: NaiveDate,
|
||||
pub d_valuta: NaiveDate,
|
||||
|
|
Loading…
Reference in a new issue