alllllmost

main
Bel LaPointe 2023-10-24 06:57:20 -06:00
parent f106442299
commit c5e36df111
2 changed files with 134 additions and 17 deletions

View File

@ -1,6 +1,7 @@
package ledger package ledger
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -66,14 +67,18 @@ type transaction struct {
recipients []transactionRecipient recipients []transactionRecipient
} }
func (t transaction) empty() bool {
return fmt.Sprint(t) == fmt.Sprint(transaction{})
}
type transactionRecipient struct { type transactionRecipient struct {
name string name string
value float64 value float64
currency string currency string
} }
func (t transaction) empty() bool { func (t transactionRecipient) empty() bool {
return fmt.Sprint(t) == fmt.Sprint(transaction{}) return t == (transactionRecipient{})
} }
func (file File) transactions() ([]transaction, error) { func (file File) transactions() ([]transaction, error) {
@ -83,9 +88,11 @@ func (file File) transactions() ([]transaction, error) {
} }
defer f.Close() defer f.Close()
var r io.Reader = f
result := make([]transaction, 0) result := make([]transaction, 0)
for { for {
one, err := readTransaction(f) newr, one, err := readTransaction(r)
if !one.empty() { if !one.empty() {
result = append(result, one) result = append(result, one)
} }
@ -95,51 +102,161 @@ func (file File) transactions() ([]transaction, error) {
if err != nil { if err != nil {
return result, err return result, err
} }
r = newr
} }
} }
func readTransaction(r io.Reader) (transaction, error) { func readTransaction(r io.Reader) (io.Reader, transaction, error) {
result := transaction{} result := transaction{}
var err error var err error
if r, err = readTransactionLeadingWhitespace(r); err != nil {
return r, transaction{}, err
}
if result.date, err = readTransactionDate(r); err != nil { if result.date, err = readTransactionDate(r); err != nil {
return result, fmt.Errorf("did not find transaction date: %w", err) if err == io.EOF {
return r, transaction{}, err
}
return nil, result, fmt.Errorf("did not find transaction date: %w", err)
} }
if result.description, err = readTransactionDescription(r); err != nil { if result.description, err = readTransactionDescription(r); err != nil {
return result, fmt.Errorf("did not find transaction description: %w", err) return nil, result, fmt.Errorf("did not find transaction description: %w", err)
} }
return transaction{}, errors.New("not impl: reading payee, recipients") for {
newR, recipient, err := readTransactionRecipient(r)
r = newR
if !recipient.empty() {
result.recipients = append(result.recipients, recipient)
}
if err != nil || recipient.empty() {
return r, result, err
}
}
} }
// from "\s*.* read \s*"
func readTransactionLeadingWhitespace(r io.Reader) (io.Reader, error) {
for {
b, err := readOne(r)
if err != nil {
return r, err
}
if b != 0 && !isSpaceByte(b) {
return io.MultiReader(bytes.NewReader([]byte{b}), r), nil
}
}
}
// from "20yy-mm-dd.* read "20yy-mm-dd"
func readTransactionDate(r io.Reader) (string, error) { func readTransactionDate(r io.Reader) (string, error) {
var firstByte [1]byte startOfDate := []byte{}
for firstByte[0] < '0' || firstByte[0] > '9' { for len(startOfDate) == 0 {
if _, err := r.Read(firstByte[:]); err != nil { b, err := readOne(r)
if err != nil {
return "", err return "", err
} }
if '0' <= b && b <= '9' {
startOfDate = append(startOfDate, b)
}
} }
restOfDate := make([]byte, len("2006-01-02")-1) restOfDate := make([]byte, len("2006-01-02")-len(startOfDate))
if _, err := r.Read(restOfDate); err != nil { if _, err := r.Read(restOfDate); err != nil {
return "", err return "", err
} }
return fmt.Sprintf("%s%s", firstByte, restOfDate), nil return fmt.Sprintf("%s%s", startOfDate, restOfDate), nil
} }
// from "\s*.*\n.*" read \s*.*\n.*"
func readTransactionDescription(r io.Reader) (string, error) { func readTransactionDescription(r io.Reader) (string, error) {
result := make([]byte, 0, 16) result := make([]byte, 0, 16)
var firstByte [1]byte
for { for {
if _, err := r.Read(firstByte[:]); err != nil { b, err := readOne(r)
if err != nil {
return "", err return "", err
} }
if firstByte[0] == '\n' { if b == '\n' {
break break
} }
result = append(result, firstByte[0]) result = append(result, b)
} }
return strings.TrimSpace(string(result)), nil return strings.TrimSpace(string(result)), nil
} }
// from "\s+.*\n?.*" read "\s+.*\n?"
func readTransactionRecipient(r io.Reader) (io.Reader, transactionRecipient, error) {
if b, err := readOne(r); err != nil {
return r, transactionRecipient{}, err
} else if !isSpaceByte(b) {
return r, transactionRecipient{}, fmt.Errorf("didnt find leading whitespace for transaction recipient")
}
r, err := readTransactionLeadingWhitespace(r)
if err != nil {
return r, transactionRecipient{}, err
}
result := transactionRecipient{}
for {
b, err := readOne(r)
if err != nil {
if result.empty() {
return nil, result, err
}
return nil, result, fmt.Errorf("failed to read transaction recipient name after skipping whitespace: %w", err)
} else if !isSpaceByte(b) {
result.name += string([]byte{b})
continue
}
break
}
if result.name == "" {
return nil, result, fmt.Errorf("did not parse any name for transaction recipient")
}
for {
b, err := readOne(r)
if err != nil {
return r, result, err
}
if isSpaceByte(b) && b != '\n' {
continue
}
break
}
if b, err := readOne(r); err != nil {
return r, result, err
} else if isSpaceByte(b) {
return r, result, err
} else if newr, value, currency, err := readTransactionValueCurrency(io.MultiReader(bytes.NewReader([]byte{b}), r)); err != nil {
return r, result, fmt.Errorf("failed to read transaction recipient's value and currency: %w", err)
} else {
r = newr
result.value = value
result.currency = currency
}
return r, result, err
}
func readOne(r io.Reader) (byte, error) {
var firstByte [1]byte
_, err := r.Read(firstByte[:])
return firstByte[0], err
}
func isSpaceByte(b byte) bool {
return len(bytes.TrimSpace([]byte{b})) == 0
}
func isNumericByte(b byte) bool {
return '0' <= b && b <= '9' || b == '.' || b == '-'
}
func readTransactionValueCurrency(r io.Reader) (io.Reader, float64, string, error) {
return nil, 0, "", errors.New("not impl read a value currency")
}

View File

@ -115,7 +115,7 @@ func TestReadTransaction(t *testing.T) {
for name, d := range cases { for name, d := range cases {
c := d c := d
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
got, err := readTransaction(strings.NewReader(c.input)) _, got, err := readTransaction(strings.NewReader(c.input))
if err != c.err { if err != c.err {
t.Error(err) t.Error(err)
} }