diff --git a/ledger/transaction.go b/ledger/transaction.go index 73bb692..a498440 100644 --- a/ledger/transaction.go +++ b/ledger/transaction.go @@ -7,6 +7,8 @@ import ( "io" "os" "regexp" + "strconv" + "unicode" ) type transaction struct { @@ -69,11 +71,28 @@ func readTransaction(r *bufio.Reader) (transaction, error) { } else if len(dateDescriptionMatches[0]) != 3 { return transaction{}, fmt.Errorf("bad first line: %v submatches: %q", len(dateDescriptionMatches[0]), firstLine) } + result := transaction{ + date: string(dateDescriptionMatches[0][1]), + description: string(dateDescriptionMatches[0][2]), + } - date := string(dateDescriptionMatches[0][1]) - description := string(dateDescriptionMatches[0][2]) - - return transaction{}, fmt.Errorf("need to parse more of %q/%q", date, description) + for { + name, value, currency, err := readTransactionAccount(r) + if name != "" { + if currency == "" { + result.payee = name + } else { + result.recipients = append(result.recipients, transactionRecipient{ + name: name, + value: value, + currency: currency, + }) + } + } + if name == "" || err != nil { + return result, err + } + } } func readTransactionLeadingWhitespace(r *bufio.Reader) { @@ -96,8 +115,17 @@ func readTransactionLeadingWhitespace(r *bufio.Reader) { } func readTransactionLine(r *bufio.Reader) ([]byte, error) { + for { + b, err := _readTransactionLine(r) + if err != nil || (len(bytes.TrimSpace(b)) > 0 && bytes.TrimSpace(b)[0] != '#') { + return b, err + } + } +} + +func _readTransactionLine(r *bufio.Reader) ([]byte, error) { b, err := r.Peek(2048) - if err != nil && err != io.EOF { + if len(b) == 0 { return nil, err } @@ -108,5 +136,47 @@ func readTransactionLine(r *bufio.Reader) ([]byte, error) { b2 := make([]byte, endOfLine) n, err := r.Read(b2) + if err == io.EOF { + err = nil + } + + if check, _ := r.Peek(1); len(check) == 1 && check[0] == '\n' { + r.Read(make([]byte, 1)) + } + return b2[:n], err } + +func readTransactionAccount(r *bufio.Reader) (string, float64, string, error) { + line, err := readTransactionLine(r) + if err != nil { + return "", 0, "", err + } + + if len(line) > 0 && !unicode.IsSpace(rune(line[0])) { + r2 := *r + *r = *bufio.NewReader(io.MultiReader(bytes.NewReader(line), &r2)) + return "", 0, "", nil + } + + fields := bytes.Fields(line) + switch len(fields) { + case 1: // payee + return string(fields[0]), 0, "", nil + case 2: // $00.00 + b := bytes.TrimLeft(fields[1], "$") + value, err := strconv.ParseFloat(string(b), 64) + if err != nil { + return "", 0, "", err + } + return string(fields[0]), value, string(USD), nil + case 3: // 00.00 XYZ + value, err := strconv.ParseFloat(string(fields[2]), 64) + if err != nil { + return "", 0, "", err + } + return string(fields[0]), value, string(fields[3]), nil + default: + return "", 0, "", fmt.Errorf("cannot interpret %q", line) + } +}