~whereswaldon/rosebud

743da9fe78db87ba3e86f78a8c73cf9c8432c1ae — Chris Waldon 1 year, 2 months ago 9028c69
appwidget,cmd/rosebud,theme: switch ledger pkg to personal fork

I needed some features that aren't available upstream currently, so I've
created a minor fork and opened issues about them upstream.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
6 files changed, 53 insertions(+), 58 deletions(-)

M appwidget/tx-form.go
M cmd/rosebud/main.go
M go.mod
M go.sum
M main.go
M theme/transaction.go
M appwidget/tx-form.go => appwidget/tx-form.go +15 -18
@@ 1,14 1,13 @@
package appwidget

import (
	"math/big"
	"strconv"
	"time"

	"gioui.org/layout"
	"gioui.org/widget"
	"git.sr.ht/~whereswaldon/ledger"
	"git.sr.ht/~whereswaldon/ledger/decimal"
	"git.sr.ht/~whereswaldon/rosebud/ds"
	"github.com/howeyc/ledger"
	"golang.org/x/exp/constraints"
)



@@ 32,11 31,7 @@ func (t *TxRow) Populated() bool {
}

func (t *TxRow) Value() ledger.Account {
	var bal *big.Rat = nil
	balance, err := strconv.ParseFloat(t.Amount.Text(), 64)
	if err == nil {
		bal = big.NewRat(0, 1).SetFloat64(balance)
	}
	bal, _ := decimal.NewFromString(t.Amount.Text())

	return ledger.Account{
		Name:    t.Account.Editor.Text(),


@@ 68,25 63,25 @@ func (t *TxForm) SetAccountSuggestions(suggestions []string) {
// balanced, but has an empty amount with an inferred value, the index of
// the empty accound and its inferred value are returned. If there is no
// inferred value, that return value will be nil.
func balance(tx *ledger.Transaction) (emptyIdx int, value *big.Rat, ok bool) {
	total := big.NewRat(0, 1)
func balance(tx *ledger.Transaction) (emptyIdx int, value decimal.Decimal, ok bool) {
	total := decimal.Zero
	empty := 0
	for i, a := range tx.AccountChanges {
		if a.Balance == nil {
		if a.Balance == decimal.Zero {
			empty++
			emptyIdx = i
		} else {
			total.Add(total, a.Balance)
			total = total.Add(a.Balance)
		}
	}
	if empty > 1 {
		return 0, nil, false
		return 0, decimal.Zero, false
	}
	if total.Sign() == 0 {
		// Totals to zero, we're balanced.
		return -1, nil, true
		return -1, decimal.Zero, true
	}
	value = total.Neg(total)
	value = total.Neg()
	return emptyIdx, value, true
}



@@ 100,6 95,8 @@ func (t *TxForm) Layout(gtx C, w layout.Widget) D {
			t.value.AccountChanges = append(t.value.AccountChanges, val)
			if val.Name == "" {
				t.Rows[i].Message = "Account name is required"
			} else {
				t.Rows[i].Message = ""
			}
		}
	}


@@ 109,11 106,11 @@ func (t *TxForm) Layout(gtx C, w layout.Widget) D {
	} else {
		t.DateEditor.SetText(time.Now().Format("2006-01-02"))
	}
	t.Rows = ds.EnsureFilledSize(t.Rows, max(populated+1, 2))
	emptyIdx, emptyValue, isBalanced := balance(&t.value)
	if isBalanced && emptyValue != nil {
		t.Rows[emptyIdx].Hint = emptyValue.FloatString(2)
	if isBalanced && emptyIdx >= 0 {
		t.Rows[emptyIdx].Hint = emptyValue.StringFixedBank()
	}
	t.Rows = ds.EnsureFilledSize(t.Rows, max(populated+1, 2))
	dims := w(gtx)
	for i := range t.Rows {
		t.events = append(t.events, t.Rows[i].Account.Events()...)

M cmd/rosebud/main.go => cmd/rosebud/main.go +20 -21
@@ 5,7 5,6 @@ import (
	"image"
	"io"
	"log"
	"math/big"
	"os"
	"path/filepath"
	"strings"


@@ 24,10 23,11 @@ import (
	"git.sr.ht/~gioverse/skel/bus"
	"git.sr.ht/~gioverse/skel/future"
	"git.sr.ht/~gioverse/skel/window"
	"git.sr.ht/~whereswaldon/ledger"
	"git.sr.ht/~whereswaldon/ledger/decimal"
	"git.sr.ht/~whereswaldon/rosebud/appwidget"
	"git.sr.ht/~whereswaldon/rosebud/appwidget/apptheme"
	"git.sr.ht/~whereswaldon/rosebud/theme"
	"github.com/howeyc/ledger"
	"github.com/lithammer/fuzzysearch/fuzzy"
	"golang.org/x/exp/maps"
)


@@ 169,7 169,7 @@ func (ui *UI) requestFiles() {
			if err := os.MkdirAll(cacheDir, 0o755); err != nil {
				return LoadInfo{}, fmt.Errorf("unable to make application cache dir: %w", err)
			}
			file, err := ui.Explorer.ChooseFile("txt", "ledger")
			file, err := ui.Explorer.ChooseFile()
			if err != nil {
				return LoadInfo{}, err
			}


@@ 195,7 195,7 @@ func (ui *UI) requestFiles() {
				}
			}()
			tee := io.TeeReader(file, backupFile)
			transactions, err := ledger.ParseLedger(tee)
			transactions, err := ledger.ParseNamedLedger(outFilePath, tee)
			if err != nil {
				return LoadInfo{}, fmt.Errorf("failed parsing input file: %w", err)
			}


@@ 210,12 210,16 @@ func (ui *UI) requestFiles() {
			}, nil
		},
		func(li LoadInfo, err error) bool {
			ui.Txs = li.Txs
			ui.Tree = li.Tree
			ui.Balances = li.Balances
			ui.OutFilePath = li.OutFilePath
			ui.CacheFilePath = li.CacheFilePath
			ui.Stage = Ready
			if err != nil {
				log.Printf("failed loading: %v", err)
			} else {
				ui.Txs = li.Txs
				ui.Tree = li.Tree
				ui.Balances = li.Balances
				ui.OutFilePath = li.OutFilePath
				ui.CacheFilePath = li.CacheFilePath
				ui.Stage = Ready
			}
			return true
		})
}


@@ 300,7 304,7 @@ func (u *UI) layoutAccountTreeNode(gtx C, at *AccountTreeNode) D {
				macro := op.Record(gtx.Ops)
				layout.E.Layout(gtx, func(gtx C) D {
					return component.Surface(u.Theme.Th).Layout(gtx, func(gtx C) D {
						return layout.UniformInset(unit.Dp(2)).Layout(gtx, material.Body2(u.Theme.Th, "Residue: "+at.Residue.FloatString(2)).Layout)
						return layout.UniformInset(unit.Dp(2)).Layout(gtx, material.Body2(u.Theme.Th, "Residue: "+at.Residue.StringFixedBank()).Layout)
					})
				})
				op.Defer(gtx.Ops, macro.Stop())


@@ 364,7 368,7 @@ func LayoutLogo(gtx C, th *material.Theme) D {

type AccountTreeNode struct {
	ledger.Account
	Residue  *big.Rat
	Residue  decimal.Decimal
	Children []*AccountTreeNode
}



@@ 379,19 383,14 @@ func BuildAccountTree(accounts []*ledger.Account) *AccountTreeNode {

func computeResidues(node *AccountTreeNode) {
	if len(node.Children) < 1 {
		node.Residue = big.NewRat(0, 1)
		node.Residue = decimal.Zero
		return
	}
	node.Residue = &big.Rat{}
	if node.Balance != nil {
		node.Residue.Set(node.Balance)
	}
	node.Residue = node.Balance
	for _, child := range node.Children {
		computeResidues(child)
		if node.Balance != nil {
			balance := *child.Balance
			node.Residue = node.Residue.Add(node.Residue, balance.Neg(&balance))
		}
		balance := child.Balance
		node.Residue = node.Residue.Add(balance.Neg())
	}
}


M go.mod => go.mod +3 -3
@@ 6,7 6,7 @@ require (
	gioui.org v0.0.0-20221122135904-dee53b364560
	gioui.org/x v0.0.0-20221121204253-9da08d942944
	git.sr.ht/~gioverse/skel v0.0.0-20220916150537-2f38f089e413
	github.com/howeyc/ledger v0.3.5
	git.sr.ht/~whereswaldon/ledger v0.0.0-20221201021135-9858bd9f1e74
	github.com/lithammer/fuzzysearch v1.1.5
	golang.org/x/exp v0.0.0-20221114191408-850992195362
)


@@ 15,16 15,16 @@ require (
	gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 // indirect
	gioui.org/shader v1.0.6 // indirect
	git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0 // indirect
	github.com/alfredxing/calc v0.0.0-20180827002445-77daf576f976 // indirect
	github.com/benoitkugler/textlayout v0.1.3 // indirect
	github.com/gioui/uax v0.2.1-0.20220819135011-cda973fac06d // indirect
	github.com/go-text/typesetting v0.0.0-20220411150340-35994bc27a7b // indirect
	github.com/godbus/dbus/v5 v5.0.6 // indirect
	github.com/joyt/godate v0.0.0-20150226210126-7151572574a7 // indirect
	github.com/marcmak/calc v0.0.0-20150509200512-5bbbfc3b3149 // indirect
	golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 // indirect
	golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 // indirect
	golang.org/x/sys v0.1.0 // indirect
	golang.org/x/text v0.3.7 // indirect
)

replace gioui.org => git.sr.ht/~whereswaldon/gio v0.0.0-20221026194450-4331d4bad0ee
replace gioui.org => git.sr.ht/~whereswaldon/gio v0.0.0-20221130215233-799cf1866570

M go.sum => go.sum +9 -6
@@ 8,10 8,16 @@ gioui.org/x v0.0.0-20221121204253-9da08d942944 h1:1qB+TI3FwPFWzyqF6LzW4jhC3Vc+Lb
gioui.org/x v0.0.0-20221121204253-9da08d942944/go.mod h1:uhVlN625ysZfFEbEdxt+wgyCGW2sh5p6SYs9dr+w+u4=
git.sr.ht/~gioverse/skel v0.0.0-20220916150537-2f38f089e413 h1:rqEp9NqS0icb0xxSWpUe11sHf0bhwobxd+O6Wei6wGc=
git.sr.ht/~gioverse/skel v0.0.0-20220916150537-2f38f089e413/go.mod h1:DtJGzJPf/ONJskXDaxORcVjIFpKWN7mBq0R8j7c4BQM=
git.sr.ht/~whereswaldon/gio v0.0.0-20221026194450-4331d4bad0ee h1:8XlfYeHKNtXPFLZ52vVxHm6JY+s0LcAgVV8moBS3BVQ=
git.sr.ht/~whereswaldon/gio v0.0.0-20221026194450-4331d4bad0ee/go.mod h1:GN091SCcGAfHfQiSOetXx7Abdy+8nmONj0ZN63Xxf7w=
git.sr.ht/~whereswaldon/gio v0.0.0-20221130215233-799cf1866570 h1:YJwB0VWnSNrcwXsdgLmFvv81DmtLCkBnN1LcjHV7UYE=
git.sr.ht/~whereswaldon/gio v0.0.0-20221130215233-799cf1866570/go.mod h1:GN091SCcGAfHfQiSOetXx7Abdy+8nmONj0ZN63Xxf7w=
git.sr.ht/~whereswaldon/ledger v0.0.0-20221201014303-8b8bd21c19ce h1:5r//Qmi7gajkHXN/9e7zOjba+gL2hMGu2/+wYcrnJpw=
git.sr.ht/~whereswaldon/ledger v0.0.0-20221201014303-8b8bd21c19ce/go.mod h1:fgkfUSZLCQMS9ahsdW7nU2Lgyd1cOHx8gN6H16ToTDs=
git.sr.ht/~whereswaldon/ledger v0.0.0-20221201021135-9858bd9f1e74 h1:TLzqsgV55Zz2mK69oaz7GNlpju9Q7A0ErIVahW4rWaw=
git.sr.ht/~whereswaldon/ledger v0.0.0-20221201021135-9858bd9f1e74/go.mod h1:fgkfUSZLCQMS9ahsdW7nU2Lgyd1cOHx8gN6H16ToTDs=
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0 h1:bGG/g4ypjrCJoSvFrP5hafr9PPB5aw8SjcOWWila7ZI=
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0/go.mod h1:+axXBRUTIDlCeE73IKeD/os7LoEnTKdkp8/gQOFjqyo=
github.com/alfredxing/calc v0.0.0-20180827002445-77daf576f976 h1:+jyVKPjl5Y39thM0ZlVrRqKjSO/Upr5tP9ZQGELv8gw=
github.com/alfredxing/calc v0.0.0-20180827002445-77daf576f976/go.mod h1:/HQknSiD7YKT15DoHXuiXezQfNPBUm8PeqFaTxeA3HU=
github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE=
github.com/benoitkugler/textlayout v0.0.5/go.mod h1:puH4v13Uz7uIhIH0XMk5jgc8U3MXcn5r3VlV9K8n0D8=
github.com/benoitkugler/textlayout v0.1.3 h1:Jv0E28xDkke3KrWle90yOLtBmZsUqXLBy70lZRfbKN0=


@@ 23,14 29,11 @@ github.com/go-text/typesetting v0.0.0-20220411150340-35994bc27a7b h1:WINlj3ANt+C
github.com/go-text/typesetting v0.0.0-20220411150340-35994bc27a7b/go.mod h1:ZNYu5saGoMOqtkVH5T8onTwhzenDUVszI+5WFHJRaxQ=
github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/howeyc/ledger v0.3.5 h1:9EfDs73TzUUeK9W5spgAR1ELW5Ywd9lbd6ha25sgDf0=
github.com/howeyc/ledger v0.3.5/go.mod h1:Bt65DBbqSGABPEugJXzcbH1KOi3QBjIk/v6/AtoTkcM=
github.com/joyt/godate v0.0.0-20150226210126-7151572574a7 h1:2wH5antjhmU3EuWyidm0lJ4B9hGMpl5lNRo+M9uGJ5A=
github.com/joyt/godate v0.0.0-20150226210126-7151572574a7/go.mod h1:R+UgFL3iylLhx9N4w35zZ2HdhDlgorRDx4SxbchWuN0=
github.com/lithammer/fuzzysearch v1.1.5 h1:Ag7aKU08wp0R9QCfF4GoGST9HbmAIeLP7xwMrOBEp1c=
github.com/lithammer/fuzzysearch v1.1.5/go.mod h1:1R1LRNk7yKid1BaQkmuLQaHruxcC4HmAH30Dh61Ih1Q=
github.com/marcmak/calc v0.0.0-20150509200512-5bbbfc3b3149 h1:QaKQMdY9TmwTq+UPXBo1hGi95iguDE7q7sPvt6RqLiA=
github.com/marcmak/calc v0.0.0-20150509200512-5bbbfc3b3149/go.mod h1:op87dInbDFPr69TsmmZoW0q4hj8LV8VZYaG4p1rDzLY=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
golang.org/x/exp v0.0.0-20221114191408-850992195362 h1:NoHlPRbyl1VFI6FjwHtPQCN7wAMXI6cKcqrmXhOOfBQ=
golang.org/x/exp v0.0.0-20221114191408-850992195362/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0=

M main.go => main.go +1 -1
@@ 20,7 20,7 @@ import (
	"gioui.org/widget/material"
	"gioui.org/x/component"
	"git.sr.ht/~whereswaldon/rosebud/theme"
	"github.com/howeyc/ledger"
	"git.sr.ht/~whereswaldon/ledger"

	"gioui.org/font/gofont"
)

M theme/transaction.go => theme/transaction.go +5 -9
@@ 6,7 6,7 @@ import (
	"gioui.org/unit"
	"gioui.org/widget/material"
	"gioui.org/x/component"
	"github.com/howeyc/ledger"
	"git.sr.ht/~whereswaldon/ledger"
)

type (


@@ 22,13 22,9 @@ type AccountStyle struct {
func Account(th *material.Theme, account ledger.Account) AccountStyle {
	var a AccountStyle
	a.Name = material.Body1(th, account.Name)
	if account.Balance != nil {
		a.Balance = material.Body1(th, account.Balance.FloatString(2))
		if account.Balance.Sign() < 0 {
			a.Balance.Color = SecondaryDark
		}
	} else {
		a.Balance = material.Body1(th, "")
	a.Balance = material.Body1(th, account.Balance.StringFixedBank())
	if account.Balance.Sign() < 0 {
		a.Balance.Color = SecondaryDark
	}
	return a
}


@@ 58,7 54,7 @@ type AccountChangeStyle struct {
func AccountChange(th *material.Theme, account ledger.Account, comment string) AccountChangeStyle {
	var a AccountChangeStyle
	a.Name = material.Body1(th, account.Name)
	a.Balance = material.Body1(th, account.Balance.FloatString(2))
	a.Balance = material.Body1(th, account.Balance.StringFixedBank())
	if account.Balance.Sign() < 0 {
		a.Balance.Color = SecondaryDark
	}