cleaner(ish) code, support for rules
This commit is contained in:
parent
8b60f2b602
commit
e45c36e6e3
3 changed files with 87 additions and 42 deletions
3
Bank.cs
3
Bank.cs
|
@ -1,6 +1,7 @@
|
||||||
namespace BunyMuny {
|
namespace BunyMuny {
|
||||||
public enum Bank {
|
public enum Bank {
|
||||||
ME,
|
ME,
|
||||||
NAB
|
NAB,
|
||||||
|
Other
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
114
Program.cs
114
Program.cs
|
@ -2,64 +2,98 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
using System.CommandLine.DragonFruit;
|
using System.CommandLine.DragonFruit;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using CsvHelper;
|
using CsvHelper;
|
||||||
using CsvHelper.Configuration.Attributes;
|
|
||||||
|
|
||||||
namespace BunyMuny {
|
namespace BunyMuny {
|
||||||
class Program {
|
internal class Program {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// BunyMuny parses the CSV output of various bank statement listings and converts it to something more human readable with nice visualisations.
|
/// BunyMuny parses the CSV output of various bank statement listings and converts it to something more human readable with nice visualisations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="file">The CSV file to read</param>
|
/// <param name="file">The CSV file to read</param>
|
||||||
/// <param name="rules">The JSON file to use for rules when parsing statement descriptions</param>
|
/// <param name="ruleFile">The JSON file to use for rules when parsing statement descriptions</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
static int Main(string file = "test.csv", string rules = "rules.json") {
|
static int Main(string file = "test.csv", string ruleFile = "rules.json") {
|
||||||
Bank bank = Bank.ME;
|
var bank = Bank.Other;
|
||||||
var statements = new List<Statement>();
|
var statements = new List<Statement>();
|
||||||
|
List<Rule> rules;
|
||||||
|
|
||||||
using (var sr = new StreamReader(file)) {
|
using (var ruleStreamReader = new StreamReader(ruleFile)) {
|
||||||
using (var csv = new CsvReader(sr, CultureInfo.InvariantCulture)) {
|
var jsonOptions = new JsonSerializerOptions();
|
||||||
csv.Read();
|
jsonOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
|
||||||
csv.ReadHeader();
|
jsonOptions.WriteIndented = true;
|
||||||
// get the first line of the CSV file (the header) as a string
|
rules = JsonSerializer.Deserialize<List<Rule>>(ruleStreamReader.ReadToEnd(), jsonOptions);
|
||||||
string header = csv.Parser.Context.RawRecord;
|
}
|
||||||
if (header == null) {
|
|
||||||
Console.WriteLine("File is empty 0uo");
|
using var sr = new StreamReader(file);
|
||||||
|
using var csv = new CsvReader(sr, CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
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("$", ""));
|
||||||
|
var ruleValues = MatchAgainstRules(rules, csv.GetField("Description"));
|
||||||
|
|
||||||
|
statements.Add(new Statement() {
|
||||||
|
Date = DateTime.ParseExact(csv.GetField("Date"), "dd/MM/yyyy", CultureInfo.InvariantCulture),
|
||||||
|
OriginalDescription = csv.GetField("Description"),
|
||||||
|
Description = ruleValues.Description,
|
||||||
|
Category = ruleValues.Category,
|
||||||
|
Value = value
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Bank.NAB:
|
||||||
|
Console.WriteLine("Unimplemented");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
|
||||||
else if (header == "Date,Description,Debits and credits,Balance") {
|
|
||||||
bank = Bank.ME;
|
|
||||||
}
|
|
||||||
else if (header == "Whatever NAB uses I guess") {
|
|
||||||
bank = Bank.NAB;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (csv.Read()) {
|
case Bank.Other:
|
||||||
switch (bank) {
|
Console.WriteLine("Unknown bank!");
|
||||||
case Bank.ME:
|
return 1;
|
||||||
double value = double.Parse(csv.GetField("Debits and credits").TrimStart().Replace("$", ""));
|
|
||||||
statements.Add(new Statement() {
|
|
||||||
Date = DateTime.ParseExact(csv.GetField("Date"), "dd/MM/yyyy", CultureInfo.InvariantCulture),
|
|
||||||
Description = csv.GetField("Description"),
|
|
||||||
Category = "Unknown",
|
|
||||||
Value = value
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Console.WriteLine(":(");
|
Console.WriteLine(":(");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var statement in statements) {
|
|
||||||
Console.WriteLine(statement);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var statement in statements) {
|
||||||
|
Console.WriteLine(statement);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
12
Statement.cs
12
Statement.cs
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace BunyMuny {
|
namespace BunyMuny {
|
||||||
public class Statement {
|
public class Statement {
|
||||||
|
@ -14,7 +15,16 @@ namespace BunyMuny {
|
||||||
public string Category;
|
public string Category;
|
||||||
|
|
||||||
public override string ToString() {
|
public override string ToString() {
|
||||||
return $"${Math.Abs(Value)} {(Value < 0 ? "to" : "from")} {(Description)} on {Date.ToString("MMM d yyyy")}";
|
// e.g.: Debit: $5.00 --> Lynnear Software (Personal) on Apr 2 2020
|
||||||
|
return String.Format(
|
||||||
|
"{0} ${1:0.##} {2} {3} ({4}) on {5:MMM d yyyy}",
|
||||||
|
Value < 0 ? "Credit:" : "Debit: ",
|
||||||
|
Math.Abs(Value),
|
||||||
|
Value < 0 ? "-->" : "<--",
|
||||||
|
Description,
|
||||||
|
Category ?? "Unknown",
|
||||||
|
Date
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue