From f6b8b92bff34eaab665e61a4d1670d1ac2c1a2f6 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Sun, 14 Jul 2024 14:24:29 -0600 Subject: [PATCH] impl and test src.ledger.Files.Amend(old, new Delta) --- src/ledger/file.go | 30 +++----- src/ledger/file_test.go | 121 +++++++++++++++++++++++++++++---- src/ledger/transaction.go | 42 ++++++++++-- src/ledger/transaction_test.go | 7 +- 4 files changed, 155 insertions(+), 45 deletions(-) diff --git a/src/ledger/file.go b/src/ledger/file.go index 287f7c1..07f6ba5 100644 --- a/src/ledger/file.go +++ b/src/ledger/file.go @@ -26,10 +26,15 @@ func NewFiles(p string, q ...string) (Files, error) { } func (files Files) Amend(old, now Delta) error { + if now.isSet { + return fmt.Errorf("cannot ammend: immutable isSet is set") + } + xactions, err := files.transactions() if err != nil { return err } + var transaction transaction for _, xaction := range xactions { if xaction.name != old.transaction { @@ -39,23 +44,13 @@ func (files Files) Amend(old, now Delta) error { break } - inverted := transaction.deltas() - for i := range inverted { - inverted[i].Value *= -1 - } - - if now.isSet { - return fmt.Errorf("cannot ammend: immutable isSet is set") - } - transaction.date = now.Date - transaction.description = now.Description if transaction.payee == old.Name { if len(transaction.recipients) != 1 { return fmt.Errorf("cannot amend: modifying original payee, but many recipients cant share new value") } - old.Value *= -1 - old.Name = transaction.recipients[0].name + transaction.payee, transaction.recipients[0].name, transaction.recipients[0].value = transaction.recipients[0].name, transaction.payee, transaction.recipients[0].value*-1.0 } + idx := -1 for i, recipient := range transaction.recipients { if recipient.name == old.Name && recipient.value == old.Value { @@ -63,16 +58,11 @@ func (files Files) Amend(old, now Delta) error { } } if idx == -1 { - return fmt.Errorf("cannot amend: no recipient with name %s found to set new value", old.Name) + return fmt.Errorf("cannot amend: no recipient with name %q value %.2f found in %+v to set new value", old.Name, old.Value, transaction) } - transaction.recipients[idx].name = now.Name - transaction.recipients[idx].value = now.Value - transaction.recipients[idx].currency = string(now.Currency) - amendedDeltas := transaction.deltas().Like(func(d Delta) bool { - return d.Name != transaction.payee - }) - return files.Add(transaction.payee, append(inverted, amendedDeltas...)...) + old.Value *= -1 + return files.Add(transaction.payee, []Delta{old, now}...) } func (files Files) TempGetLastNLines(n int) ([]string, error) { diff --git a/src/ledger/file_test.go b/src/ledger/file_test.go index 511ad19..2edf9b1 100644 --- a/src/ledger/file_test.go +++ b/src/ledger/file_test.go @@ -17,13 +17,94 @@ func TestFileAmend(t *testing.T) { now Delta want string }{ - //"was set payee": { - //"payee": { - //"recipient": { - //"multi recipient": { + "multi recipient": { + from: ` +2006-01-02 description + recipientA $3.45 + recipientB $6.45 + payee + `, + old: Delta{ + Date: "2006-01-02", + Name: "recipientB", + Value: 6.45, + Currency: "$", + Description: "description", + }, + now: Delta{ + Date: "2106-11-12", + Name: "recipientC", + Value: 16.45, + Currency: "T", + Description: "1description", + }, + want: ` +2006-01-02 description + recipientB $-6.45 + payee +2106-11-12 1description + recipientC 16.45 T + payee`, + }, + "recipient": { + from: ` +2006-01-02 description + recipient $3.45 + payee $-3.45 + `, + old: Delta{ + Date: "2006-01-02", + Name: "recipient", + Value: 3.45, + Currency: "$", + Description: "description", + }, + now: Delta{ + Date: "2106-11-12", + Name: "1recipient", + Value: 13.45, + Currency: "T", + Description: "1description", + }, + want: ` +2006-01-02 description + recipient $-3.45 + payee +2106-11-12 1description + 1recipient 13.45 T + payee`, + }, + "payee": { + from: ` +2006-01-02 description + recipient $3.45 + payee + `, + old: Delta{ + Date: "2006-01-02", + Name: "payee", + Value: -3.45, + Currency: "$", + Description: "description", + }, + now: Delta{ + Date: "2106-11-12", + Name: "1payee", + Value: -13.45, + Currency: "T", + Description: "1description", + }, + want: ` +2006-01-02 description + payee $3.45 + recipient +2106-11-12 1description + 1payee -13.45 T + recipient`, + }, "was set": { from: ` - 2006-01-02 description +2006-01-02 description recipient $3.45 payee `, @@ -36,11 +117,18 @@ func TestFileAmend(t *testing.T) { }, now: Delta{ Date: "2106-11-12", - Name: "recipient", - Value: 3.45, - Currency: "$", - Description: "description", + Name: "1recipient", + Value: 13.45, + Currency: "T", + Description: "1description", }, + want: ` +2006-01-02 description + recipient $-3.45 + payee +2106-11-12 1description + 1recipient 13.45 T + payee`, }, } @@ -48,21 +136,28 @@ func TestFileAmend(t *testing.T) { c := d t.Run(name, func(t *testing.T) { p := path.Join(t.TempDir(), "dat") - if err := os.WriteFile(p, bytes.TrimSpace([]byte(c.from)), os.ModePerm); err != nil { + if err := os.WriteFile(p, []byte(c.from), os.ModePerm); err != nil { t.Fatal(err) } files, err := NewFiles(p) if err != nil { t.Fatal(err) + } else if deltas, err := files.Deltas(); err != nil { + t.Fatal(err) + } else if filtered := deltas.Like(func(d Delta) bool { + c.old.transaction = d.transaction + return d == c.old + }); len(filtered) != 1 { + t.Fatalf("input %s didnt include old %+v in %+v", c.from, c.old, deltas) } if err := files.Amend(c.old, c.now); err != nil { t.Fatal(err) - } else if b, err := os.ReadFile(path.Join(path.Dir(p), "inbox.dat")); err != nil { + } else if b, err := os.ReadFile(path.Join(path.Dir(p), "inbox.txt")); err != nil { t.Fatal(err) - } else if b := bytes.TrimSpace(b); string(b) != c.want { - t.Fatalf("expected \n\t%s\nbut got\n\t%s", c.want, b) + } else if string(b) != c.want { + t.Fatalf("expected \n\t%q\nbut got\n\t%q\n\t%s", c.want, b, b) } }) } diff --git a/src/ledger/transaction.go b/src/ledger/transaction.go index e1a9f2c..271ebc9 100644 --- a/src/ledger/transaction.go +++ b/src/ledger/transaction.go @@ -3,10 +3,12 @@ package ledger import ( "bufio" "bytes" + "errors" "fmt" "io" "os" "regexp" + "slices" "strconv" "unicode" ) @@ -112,19 +114,47 @@ func (files Files) _transactions(file string) ([]transaction, error) { func readTransaction(name string, r *bufio.Reader) (transaction, error) { result, err := _readTransaction(name, r) - if err != nil { + if err != nil && !errors.Is(err, io.EOF) { return result, err } if result.empty() { - return result, nil - } - if result.payee == "" && len(result.recipients) < 2 { - return result, fmt.Errorf("found a transaction with no payee and less than 2 recipeints: %+v", result) + return result, err } if result.payee != "" && len(result.recipients) < 1 { return result, fmt.Errorf("found a transaction with payee but no recipeints: %+v", result) } - return result, nil + if result.payee == "" { + if len(result.recipients) < 2 { + return result, fmt.Errorf("found a transaction with no payee and less than 2 recipeints: %+v", result) + } + func() { + sumPerRecipient := map[string]float64{} + recipients := []string{} + for _, recipient := range result.recipients { + recipients = append(recipients, recipient.name) + sumPerRecipient[recipient.name] += recipient.value + } + slices.Sort(recipients) + for _, k := range recipients { + v := sumPerRecipient[k] + everyoneElse := 0.0 + for j := range sumPerRecipient { + if k != j { + everyoneElse += sumPerRecipient[j] + } + } + if -1.0*v == everyoneElse { + result.payee = k + result.recipients = slices.DeleteFunc(result.recipients, func(recipient transactionRecipient) bool { + return recipient.name == k + }) + return + } + } + return + }() + } + return result, err } func _readTransaction(name string, r *bufio.Reader) (transaction, error) { diff --git a/src/ledger/transaction_test.go b/src/ledger/transaction_test.go index 2f6315c..99d8f6f 100644 --- a/src/ledger/transaction_test.go +++ b/src/ledger/transaction_test.go @@ -33,13 +33,8 @@ func TestReadTransaction(t *testing.T) { want: transaction{ date: "2003-04-05", description: "Reasoning here", - payee: "", + payee: "A:B", recipients: []transactionRecipient{ - { - name: "A:B", - value: 1.0, - currency: "$", - }, { name: "C:D", value: -1.0,