From 42be0862cac5cf6695665c0748e7a05faf515db1 Mon Sep 17 00:00:00 2001 From: Lynnesbian Date: Sun, 27 Sep 2020 15:27:16 +1000 Subject: [PATCH] support for NAB --- .gitignore | 1 + Program.cs | 82 +++++++++++++++++++++++++++++++++++------------------- 2 files changed, 54 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index 912d71a..190372f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,6 @@ bin/ obj/ test.csv +test2.csv rules.csv .~lock* diff --git a/Program.cs b/Program.cs index 59fefbe..225b239 100644 --- a/Program.cs +++ b/Program.cs @@ -22,7 +22,9 @@ using System.Globalization; using System.IO; using System.CommandLine.DragonFruit; using System.Linq; +using System.Text.RegularExpressions; using CsvHelper; +using CsvHelper.Configuration; namespace BunyMuny { internal class Program { @@ -38,38 +40,53 @@ namespace BunyMuny { List rules; using (var ruleStreamReader = new StreamReader(ruleFile)) { - using var ruleCsv = new CsvReader(ruleStreamReader,CultureInfo.InvariantCulture); + using var ruleCsv = new CsvReader(ruleStreamReader, CultureInfo.InvariantCulture); rules = ruleCsv.GetRecords().ToList(); } + // get the first line of the CSV file (the header) as a string + string header; + using (var headerReader = new StreamReader(file)) { + header = headerReader.ReadLine(); + } + using var sr = new StreamReader(file); using var csv = new CsvReader(sr, CultureInfo.InvariantCulture); + csv.Configuration.HasHeaderRecord = false; - csv.Read(); - csv.ReadHeader(); + var nabRegex = new Regex(@"^\d\d \w{3} \d\d"); - // 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; + if (header == null) { + Console.WriteLine("File is empty 0uo"); + return 1; + } + else if (header == "Date,Description,Debits and credits,Balance") { + bank = Bank.ME; + csv.Configuration.HasHeaderRecord = true; + // read in the header to allow for accessing fields by name rather than by index + csv.Read(); + csv.ReadHeader(); + } + else if (nabRegex.IsMatch(header)) { + // NAB exports don't have headers + // records look like "02 Apr 20,23.00,,,MISCELLANEOUS DEBIT,V1234 02/09 PAYPAL Sydney,1234.56 + // the columns mean: date of transaction, amount, ???, ???, category,method,remaining balance + // i don't like it >:c + bank = Bank.NAB; + } + else { + Console.WriteLine($"Unknown header: [{header}]"); } while (csv.Read()) { + decimal value; + string category; + string description; + switch (bank) { case Bank.ME: - var value = decimal.Parse(csv.GetField("Debits and credits").TrimStart().Replace("$", "")); - var (category, description) = MatchAgainstRules(rules, csv.GetField("Description")); + value = decimal.Parse(csv.GetField("Debits and credits").TrimStart().Replace("$", "")); + (category, description) = MatchAgainstRules(rules, csv.GetField("Description")); statements.Add(new Statement() { Date = DateTime.ParseExact(csv.GetField("Date"), "dd/MM/yyyy", CultureInfo.InvariantCulture), @@ -81,8 +98,17 @@ namespace BunyMuny { break; case Bank.NAB: - Console.WriteLine("Unimplemented"); - return 1; + // return 1; + value = decimal.Parse(csv.GetField(1)); + (category, description) = MatchAgainstRules(rules, csv.GetField(5)); + statements.Add(new Statement() { + Date = DateTime.ParseExact(csv.GetField(0), "dd MMM yy", CultureInfo.CurrentCulture), + OriginalDescription = csv.GetField(5), + Description = description, + Category = category, + Value = value + }); + break; case Bank.Other: Console.WriteLine("Unknown bank!"); @@ -102,16 +128,14 @@ namespace BunyMuny { Console.WriteLine("Summary:"); Console.WriteLine("=================="); - var summaries = statements. - GroupBy(s => s.Category). // group statements by category - Select(summary => new { // and then select: - Name = summary.First().Category?? "Other", // the name of the category... + var summaries = statements.GroupBy(s => s.Category). // group statements by category + Select(summary => new { + // and then select: + Name = summary.First().Category ?? "Other", // the name of the category... Total = summary.Sum(s => s.Value).ToString() // ...and the sum of all the statement's values in that category }); - var longestCategoryName = summaries. - ToList(). - Max(summary => summary.Name.Length); + var longestCategoryName = summaries.ToList().Max(summary => summary.Name.Length); foreach (var summary in summaries) { Console.WriteLine("{0}: {1}", summary.Name.PadLeft(longestCategoryName), summary.Total);