fixstmt

Tool to rename bank statements to yyyy-mm-dd format
git clone https://git.bracken.jp/fixstmt.git
Log | Files | Refs | LICENSE

commit c96b09e93294c1ff25bb12c949d8ca49044447f1
parent 6b43093695d72086c057fb27dce71e1aa8ee8ade
Author: Chris Bracken <chris@bracken.jp>
Date:   Mon,  3 Jan 2022 15:51:38 -0800

Add support for Apple Card statements

Refactors convert functions into instances of Converter interface and
automatically detects file type based on filename.

Diffstat:
Mconvert/convert.go | 78+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mconvert/convert_test.go | 60+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mmain.go | 15++++++++-------
3 files changed, 132 insertions(+), 21 deletions(-)

diff --git a/convert/convert.go b/convert/convert.go @@ -4,11 +4,41 @@ package convert import ( "fmt" + "path/filepath" + "regexp" "strings" ) -// Format: ACCTNO-YYYYmmmDD-YYYYmmmDD.pdf -func RbcDirectInvesting(fname string) (string, error) { +type Converter interface { + Type() string + CanConvert(fname string) bool + Convert(fname string) (string, error) +} + +type RbcDirectInvesting struct{} +type AppleCard struct{} + +func ConverterForFile(fname string) (Converter, error) { + converters := []Converter{&AppleCard{}, &RbcDirectInvesting{}} + for _, c := range converters { + if c.CanConvert(fname) { + return c, nil + } + } + return nil, fmt.Errorf("Unknown statement type: %s", fname) +} + +func (c *RbcDirectInvesting) Type() string { + return "RBC Direct Investing" +} + +func (c *RbcDirectInvesting) CanConvert(fname string) bool { + r := regexp.MustCompile(`\d+-\d{4}[A-Za-z]{3}\d\d-\d{4}[A-Za-z]{3}\d\d`) + return r.MatchString(fname) +} + +// Format: ACCTNO-2020Jan15-2020Feb14.pdf +func (c *RbcDirectInvesting) Convert(fname string) (string, error) { date := strings.Split(fname, "-")[2][0:9] y := date[0:4] m, err := getMonthNumber(strings.ToLower(date[4:7])) @@ -20,31 +50,53 @@ func RbcDirectInvesting(fname string) (string, error) { return newFname, nil } +func (c *AppleCard) Type() string { + return "Apple Card" +} + +func (c *AppleCard) CanConvert(fname string) bool { + return strings.HasPrefix(strings.ToLower(fname), "apple card statement ") +} + +// Format: Apple Card Statement - January 2020.pdf +func (c *AppleCard) Convert(fname string) (string, error) { + ext := filepath.Ext(fname) + basename := fname[0 : len(fname)-len(ext)] + date := strings.Split(basename, " ")[4:6] + y := date[1] + m, err := getMonthNumber(strings.ToLower(date[0])) + if err != nil { + return "", err + } + newFname := fmt.Sprintf("%s-%s.pdf", y, m) + return newFname, nil +} + func getMonthNumber(m string) (string, error) { switch { - case m == "jan": + case m == "jan" || m == "january": return "01", nil - case m == "feb": + case m == "feb" || m == "february": return "02", nil - case m == "mar": + case m == "mar" || m == "march": return "03", nil - case m == "apr": + case m == "apr" || m == "april": return "04", nil case m == "may": return "05", nil - case m == "jun": + case m == "jun" || m == "june": return "06", nil - case m == "jul": + case m == "jul" || m == "july": return "07", nil - case m == "aug": + case m == "aug" || m == "august": return "08", nil - case m == "sep": + case m == "sep" || m == "september": return "09", nil - case m == "oct": + case m == "oct" || m == "october": return "10", nil - case m == "nov": + case m == "nov" || m == "november": return "11", nil - case m == "dec": + case m == "dec" || m == "december": return "12", nil } return "", fmt.Errorf("Bad month name: %s", m) diff --git a/convert/convert_test.go b/convert/convert_test.go @@ -6,6 +6,34 @@ import ( "git.bracken.jp/fixstmt/convert" ) +func TestConverterForFile(t *testing.T) { + testCases := []struct { + name string + want string + }{ + {"123456789-2007jAn10-2007FeB09.pdf", "RBC Direct Investing"}, + {"Apple Card Statement - JaNUarY 2020.pdf", "Apple Card"}, + } + for _, tc := range testCases { + got, err := convert.ConverterForFile(tc.name) + if err != nil { + t.Errorf("Failed to get converter for file %s", tc.name) + } else if got.Type() != tc.want { + t.Errorf("In(%s) = %s; want %s", tc.name, got.Type(), tc.want) + } + } +} + +func TestConverterForFileWithUnknownType(t *testing.T) { + got, err := convert.ConverterForFile("document.pdf") + if err == nil { + t.Errorf("Expected error for file document.pdf") + } + if got != nil { + t.Errorf("In(document.pdf) = %s; want nil", got.Type()) + } +} + func TestRbcDirectInvesting(t *testing.T) { testCases := []struct { name string @@ -19,8 +47,38 @@ func TestRbcDirectInvesting(t *testing.T) { {"2345678901-2022Oct31-2022Nov30.pdf", "2022-11-30.pdf"}, {"2345678901-2022Nov29-2023Jan28.pdf", "2023-01-28.pdf"}, } + converter := convert.RbcDirectInvesting{} + for _, tc := range testCases { + got, err := converter.Convert(tc.name) + if err != nil { + t.Errorf("Failed to convert file %s", tc.name) + } else if got != tc.want { + t.Errorf("In(%s) = %s; want %s", tc.name, got, tc.want) + } + } +} + +func TestAppleCard(t *testing.T) { + testCases := []struct { + name string + want string + }{ + {"Apple Card Statement - January 2020.pdf", "2020-01.pdf"}, + {"Apple Card Statement - February 2020.pdf", "2020-02.pdf"}, + {"Apple Card Statement - March 2020.pdf", "2020-03.pdf"}, + {"Apple Card Statement - April 2020.pdf", "2020-04.pdf"}, + {"Apple Card Statement - May 2020.pdf", "2020-05.pdf"}, + {"Apple Card Statement - June 2020.pdf", "2020-06.pdf"}, + {"Apple Card Statement - July 2020.pdf", "2020-07.pdf"}, + {"Apple Card Statement - August 2020.pdf", "2020-08.pdf"}, + {"Apple Card Statement - September 2020.pdf", "2020-09.pdf"}, + {"Apple Card Statement - October 2020.pdf", "2020-10.pdf"}, + {"Apple Card Statement - November 2020.pdf", "2020-11.pdf"}, + {"Apple Card Statement - December 2020.pdf", "2020-12.pdf"}, + } + converter := convert.AppleCard{} for _, tc := range testCases { - got, err := convert.RbcDirectInvesting(tc.name) + got, err := converter.Convert(tc.name) if err != nil { t.Errorf("Failed to convert file %s", tc.name) } else if got != tc.want { diff --git a/main.go b/main.go @@ -4,7 +4,6 @@ import ( "io/ioutil" "log" "os" - "strings" "git.bracken.jp/fixstmt/convert" ) @@ -15,12 +14,14 @@ func main() { log.Fatal(err) } for _, f := range files { - if strings.HasSuffix(f.Name(), ".pdf") { - newFname, err := convert.RbcDirectInvesting(f.Name()) - if err != nil { - log.Fatal(err) - } - os.Rename(f.Name(), newFname) + converter, err := convert.ConverterForFile(f.Name()) + if err != nil { + log.Fatal(err) } + newFname, err := converter.Convert(f.Name()) + if err != nil { + log.Fatal(err) + } + os.Rename(f.Name(), newFname) } }