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:
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)
}
}