2020-09-26 13:19:53 +00:00
|
|
|
|
/*
|
|
|
|
|
BunyMuny parses the CSV output of various bank statement listings and converts it to something more human readable with nice visualisations.
|
|
|
|
|
Copyright (C) 2020 Lynnear Software
|
|
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
|
along with this program.If not, see <https: //www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
using System;
|
2020-09-26 11:30:15 +00:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
using System.IO;
|
2020-09-26 11:59:06 +00:00
|
|
|
|
using System.CommandLine.DragonFruit;
|
2020-09-26 13:09:30 +00:00
|
|
|
|
using System.Linq;
|
2020-09-26 11:30:15 +00:00
|
|
|
|
using CsvHelper;
|
|
|
|
|
|
|
|
|
|
namespace BunyMuny {
|
2020-09-26 12:45:07 +00:00
|
|
|
|
internal class Program {
|
2020-09-26 11:59:06 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// BunyMuny parses the CSV output of various bank statement listings and converts it to something more human readable with nice visualisations.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="file">The CSV file to read</param>
|
2020-09-27 03:06:44 +00:00
|
|
|
|
/// <param name="ruleFile">The CSV file to use for rules when parsing statement descriptions</param>
|
2020-09-26 11:59:06 +00:00
|
|
|
|
/// <returns></returns>
|
2020-09-27 03:06:44 +00:00
|
|
|
|
private static int Main(string file = "test.csv", string ruleFile = "rules.csv") {
|
2020-09-26 12:45:07 +00:00
|
|
|
|
var bank = Bank.Other;
|
2020-09-26 11:30:15 +00:00
|
|
|
|
var statements = new List<Statement>();
|
2020-09-26 12:45:07 +00:00
|
|
|
|
List<Rule> rules;
|
2020-09-26 11:30:15 +00:00
|
|
|
|
|
2020-09-26 12:45:07 +00:00
|
|
|
|
using (var ruleStreamReader = new StreamReader(ruleFile)) {
|
2020-09-27 03:06:44 +00:00
|
|
|
|
using var ruleCsv = new CsvReader(ruleStreamReader,CultureInfo.InvariantCulture);
|
|
|
|
|
rules = ruleCsv.GetRecords<Rule>().ToList();
|
2020-09-26 12:45:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using var sr = new StreamReader(file);
|
|
|
|
|
using var csv = new CsvReader(sr, CultureInfo.InvariantCulture);
|
2020-09-26 11:30:15 +00:00
|
|
|
|
|
2020-09-26 12:45:07 +00:00
|
|
|
|
csv.Read();
|
|
|
|
|
csv.ReadHeader();
|
|
|
|
|
|
|
|
|
|
// get the first line of the CSV file (the header) as a string
|
|
|
|
|
var header = csv.Parser.Context.RawRecord.Trim();
|
|
|
|
|
switch (header) {
|
|
|
|
|
case null:
|
|
|
|
|
Console.WriteLine("File is empty 0uo");
|
|
|
|
|
return 1;
|
|
|
|
|
case "Date,Description,Debits and credits,Balance":
|
|
|
|
|
bank = Bank.ME;
|
|
|
|
|
break;
|
|
|
|
|
case "Whatever NAB uses I guess":
|
|
|
|
|
bank = Bank.NAB;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
Console.WriteLine($"Unknown header: [{header}]");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (csv.Read()) {
|
|
|
|
|
switch (bank) {
|
|
|
|
|
case Bank.ME:
|
|
|
|
|
var value = double.Parse(csv.GetField("Debits and credits").TrimStart().Replace("$", ""));
|
2020-09-27 03:06:44 +00:00
|
|
|
|
var (category, description) = MatchAgainstRules(rules, csv.GetField("Description"));
|
2020-09-26 12:45:07 +00:00
|
|
|
|
|
|
|
|
|
statements.Add(new Statement() {
|
|
|
|
|
Date = DateTime.ParseExact(csv.GetField("Date"), "dd/MM/yyyy", CultureInfo.InvariantCulture),
|
|
|
|
|
OriginalDescription = csv.GetField("Description"),
|
2020-09-27 03:06:44 +00:00
|
|
|
|
Description = description,
|
|
|
|
|
Category = category,
|
2020-09-26 12:45:07 +00:00
|
|
|
|
Value = value
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Bank.NAB:
|
|
|
|
|
Console.WriteLine("Unimplemented");
|
|
|
|
|
return 1;
|
2020-09-26 11:59:06 +00:00
|
|
|
|
|
2020-09-26 12:45:07 +00:00
|
|
|
|
case Bank.Other:
|
|
|
|
|
Console.WriteLine("Unknown bank!");
|
|
|
|
|
return 1;
|
2020-09-26 11:30:15 +00:00
|
|
|
|
|
2020-09-26 12:45:07 +00:00
|
|
|
|
default:
|
|
|
|
|
Console.WriteLine(":(");
|
|
|
|
|
return 1;
|
2020-09-26 11:30:15 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-26 13:09:30 +00:00
|
|
|
|
foreach (var statement in statements.
|
|
|
|
|
Where(s => s.Category != null).
|
|
|
|
|
OrderBy(s => s.Date)) {
|
2020-09-26 12:45:07 +00:00
|
|
|
|
Console.WriteLine(statement);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-26 11:30:15 +00:00
|
|
|
|
return 0;
|
|
|
|
|
}
|
2020-09-26 12:45:07 +00:00
|
|
|
|
|
|
|
|
|
private static (string Category, string Description) MatchAgainstRules(List<Rule> rules, string value) {
|
|
|
|
|
foreach (var rule in rules) {
|
|
|
|
|
if (rule.Check(value)) {
|
|
|
|
|
return (rule.Category, rule.Description);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (null, null);
|
|
|
|
|
}
|
2020-09-26 11:30:15 +00:00
|
|
|
|
}
|
|
|
|
|
}
|