Compare commits
7 Commits
8ab5a0edf5
...
4831914251
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4831914251 | ||
|
|
eb3af4b54f | ||
|
|
849a8696f5 | ||
|
|
1a2c88687f | ||
|
|
889dc48d6c | ||
|
|
881162357b | ||
|
|
62e65c47df |
@@ -3,7 +3,7 @@ package cli
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
Files FileList
|
Files FileList
|
||||||
Query struct {
|
Query struct {
|
||||||
Period string
|
Period Period
|
||||||
Sort string
|
Sort string
|
||||||
NoRounding bool
|
NoRounding bool
|
||||||
Depth int
|
Depth int
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FlagStringArray []string
|
type FlagStringArray []string
|
||||||
@@ -33,3 +34,35 @@ func (fileList *FileList) Set(s string) error {
|
|||||||
}
|
}
|
||||||
return (*FlagStringArray)(fileList).Set(s)
|
return (*FlagStringArray)(fileList).Set(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Period struct {
|
||||||
|
Start time.Time
|
||||||
|
Stop time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (period Period) Empty() bool {
|
||||||
|
return period.Stop.Sub(period.Start) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (period *Period) String() string {
|
||||||
|
return fmt.Sprintf("%s..%s", period.Start, period.Stop)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (period *Period) Set(s string) error {
|
||||||
|
if result, err := time.Parse("2006", s); err == nil {
|
||||||
|
period.Start = result
|
||||||
|
period.Stop = result.AddDate(1, 0, 0).Add(-1 * time.Minute)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if result, err := time.Parse("2006-01", s); err == nil {
|
||||||
|
period.Start = result
|
||||||
|
period.Stop = result.AddDate(0, 1, 0).Add(-1 * time.Minute)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if result, err := time.Parse("2006-01-02", s); err == nil {
|
||||||
|
period.Start = result
|
||||||
|
period.Stop = result.AddDate(0, 0, 1).Add(-1 * time.Minute)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("unimplemented format: %s", s)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package cli
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ func Main() {
|
|||||||
|
|
||||||
fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
|
fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
|
||||||
fs.Var(&config.Files, "f", "paths to files")
|
fs.Var(&config.Files, "f", "paths to files")
|
||||||
fs.StringVar(&config.Query.Period, "period", "", "period")
|
fs.Var(&config.Query.Period, "period", "period")
|
||||||
fs.StringVar(&config.Query.Sort, "S", "", "sort ie date")
|
fs.StringVar(&config.Query.Sort, "S", "", "sort ie date")
|
||||||
fs.BoolVar(&config.Query.NoRounding, "no-rounding", false, "no rounding")
|
fs.BoolVar(&config.Query.NoRounding, "no-rounding", false, "no rounding")
|
||||||
fs.IntVar(&config.Query.Depth, "depth", 0, "depth grouping")
|
fs.IntVar(&config.Query.Depth, "depth", 0, "depth grouping")
|
||||||
@@ -37,18 +38,38 @@ func Main() {
|
|||||||
}
|
}
|
||||||
cmd := positional[0]
|
cmd := positional[0]
|
||||||
|
|
||||||
likes := []ledger.Like{}
|
likePattern := ""
|
||||||
for _, query := range positional[1:] {
|
notLikePattern := ""
|
||||||
panic(query)
|
i := 1
|
||||||
|
for i < len(positional) {
|
||||||
|
switch positional[i] {
|
||||||
|
case "not":
|
||||||
|
_ = notLikePattern
|
||||||
|
panic(positional[i:])
|
||||||
|
case "and":
|
||||||
|
panic(positional[i:])
|
||||||
|
default:
|
||||||
|
if likePattern == "" {
|
||||||
|
likePattern = positional[i]
|
||||||
|
} else {
|
||||||
|
likePattern = fmt.Sprintf("%s|%s", likePattern, positional[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
deltas, err := ledgerFiles.Deltas(likes...)
|
deltas, err := ledgerFiles.Deltas()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if period := config.Query.Period; period != "" {
|
if period := config.Query.Period; !period.Empty() {
|
||||||
panic(period)
|
after := period.Start.Format("2006-01-02")
|
||||||
|
before := period.Stop.Format("2006-01-02")
|
||||||
|
deltas = deltas.Like(
|
||||||
|
ledger.LikeAfter(after),
|
||||||
|
ledger.LikeBefore(before),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if depth := config.Query.Depth; depth > 0 {
|
if depth := config.Query.Depth; depth > 0 {
|
||||||
@@ -58,7 +79,40 @@ func Main() {
|
|||||||
switch cmd[:3] {
|
switch cmd[:3] {
|
||||||
case "bal":
|
case "bal":
|
||||||
balances := deltas.Balances()
|
balances := deltas.Balances()
|
||||||
|
if likePattern != "" {
|
||||||
|
balances = balances.Like(likePattern)
|
||||||
|
}
|
||||||
|
if notLikePattern != "" {
|
||||||
|
balances = balances.NotLike(notLikePattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
FPrintBalances(os.Stdout, balances)
|
||||||
|
case "reg":
|
||||||
|
register := deltas.Register()
|
||||||
|
if likePattern != "" {
|
||||||
|
register = register.Like(likePattern)
|
||||||
|
}
|
||||||
|
if notLikePattern != "" {
|
||||||
|
register = register.NotLike(notLikePattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
var prev ledger.Balances
|
||||||
|
for _, date := range register.Dates() {
|
||||||
|
balances := register[date]
|
||||||
|
|
||||||
|
if newBalances := balances.Sub(prev).Nonzero(); len(newBalances) > 0 {
|
||||||
|
fmt.Println(date)
|
||||||
|
FPrintBalances(os.Stdout, newBalances)
|
||||||
|
}
|
||||||
|
|
||||||
|
prev = balances
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("unknown command " + positional[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FPrintBalances(w io.Writer, balances ledger.Balances) {
|
||||||
keys := []string{}
|
keys := []string{}
|
||||||
for k := range balances {
|
for k := range balances {
|
||||||
keys = append(keys, k)
|
keys = append(keys, k)
|
||||||
@@ -74,16 +128,18 @@ func Main() {
|
|||||||
|
|
||||||
format := fmt.Sprintf("%%-%ds\t%%s%%.2f\n", max)
|
format := fmt.Sprintf("%%-%ds\t%%s%%.2f\n", max)
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
for currency, v := range balances[key] {
|
currencies := []ledger.Currency{}
|
||||||
if currency != "$" {
|
for currency := range balances[key] {
|
||||||
currency += " "
|
currencies = append(currencies, currency)
|
||||||
}
|
}
|
||||||
fmt.Printf(format, key, currency, v)
|
slices.Sort(currencies)
|
||||||
|
|
||||||
|
for _, currency := range currencies {
|
||||||
|
printableCurrency := currency
|
||||||
|
if printableCurrency != "$" {
|
||||||
|
printableCurrency += " "
|
||||||
}
|
}
|
||||||
|
fmt.Fprintf(w, format, key, printableCurrency, balances[key][currency])
|
||||||
}
|
}
|
||||||
case "reg":
|
|
||||||
// register := deltas.Register()
|
|
||||||
default:
|
|
||||||
panic("unknown command " + positional[0])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,30 @@ type Balances map[string]Balance
|
|||||||
|
|
||||||
type Balance map[Currency]float64
|
type Balance map[Currency]float64
|
||||||
|
|
||||||
|
func (balances Balances) Sub(other Balances) Balances {
|
||||||
|
result := make(Balances)
|
||||||
|
for k, v := range balances {
|
||||||
|
result[k] = v.Sub(other[k])
|
||||||
|
}
|
||||||
|
for k, v := range other {
|
||||||
|
if _, ok := balances[k]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result[k] = v.Invert()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (balances Balances) Nonzero() Balances {
|
||||||
|
result := make(Balances)
|
||||||
|
for k, v := range balances {
|
||||||
|
if nonzero := v.Nonzero(); len(nonzero) > 0 {
|
||||||
|
result[k] = nonzero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func (balances Balances) NotLike(pattern string) Balances {
|
func (balances Balances) NotLike(pattern string) Balances {
|
||||||
result := make(Balances)
|
result := make(Balances)
|
||||||
p := regexp.MustCompile(pattern)
|
p := regexp.MustCompile(pattern)
|
||||||
@@ -98,6 +122,39 @@ func (balances Balances) Push(d Delta) {
|
|||||||
balances[d.Name].Push(d)
|
balances[d.Name].Push(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (balance Balance) Sub(other Balance) Balance {
|
||||||
|
return balance.Sum(other.Invert())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (balance Balance) Sum(other Balance) Balance {
|
||||||
|
result := make(Balance)
|
||||||
|
for k, v := range balance {
|
||||||
|
result[k] += v
|
||||||
|
}
|
||||||
|
for k, v := range other {
|
||||||
|
result[k] += v
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (balance Balance) Nonzero() Balance {
|
||||||
|
result := make(Balance)
|
||||||
|
for k, v := range balance {
|
||||||
|
if v != 0 {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (balance Balance) Invert() Balance {
|
||||||
|
result := make(Balance)
|
||||||
|
for k, v := range balance {
|
||||||
|
result[k] = v * -1.0
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func (balance Balance) Push(d Delta) {
|
func (balance Balance) Push(d Delta) {
|
||||||
if _, ok := balance[d.Currency]; !ok {
|
if _, ok := balance[d.Currency]; !ok {
|
||||||
balance[d.Currency] = 0
|
balance[d.Currency] = 0
|
||||||
|
|||||||
@@ -51,6 +51,26 @@ func (register Register) Names() []string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (register Register) Like(pattern string) Register {
|
||||||
|
result := make(Register)
|
||||||
|
for k, v := range register {
|
||||||
|
if got := v.Like(pattern); len(got) > 0 {
|
||||||
|
result[k] = got
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (register Register) NotLike(pattern string) Register {
|
||||||
|
result := make(Register)
|
||||||
|
for k, v := range register {
|
||||||
|
if got := v.NotLike(pattern); len(got) > 0 {
|
||||||
|
result[k] = got
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func (register Register) Dates() []string {
|
func (register Register) Dates() []string {
|
||||||
result := make([]string, 0, len(register))
|
result := make([]string, 0, len(register))
|
||||||
for k := range register {
|
for k := range register {
|
||||||
|
|||||||
Reference in New Issue
Block a user