package cli import ( "flag" "fmt" "io" "math" "os" "slices" "strings" "time" "gogs.inhome.blapointe.com/ana-ledger/src/ana" "gogs.inhome.blapointe.com/ana-ledger/src/ledger" ) func Main() { var config Config fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError) fs.Var(&config.Files, "f", "paths to files") fs.Var(&config.Query.Period, "period", "period") fs.StringVar(&config.Query.Sort, "S", "", "sort ie date") fs.BoolVar(&config.Query.NoRounding, "no-rounding", false, "no rounding") fs.StringVar(&config.Query.With, "w", "", "regexp for transactions") fs.IntVar(&config.Query.Depth, "depth", 0, "depth grouping") fs.BoolVar(&config.Query.Normalize, "n", false, "normalize with default normalizer") fs.BoolVar(&config.Query.USDOnly, "usd", false, "filter to usd") fs.BoolVar(&config.Query.NoExchanging, "no-exchanging", true, "omit currency exchanges") fs.StringVar(&config.BPI, "bpi", "", "path to bpi") if err := fs.Parse(os.Args[1:]); err != nil { panic(err) } files := config.Files.Strings() if len(files) == 0 { panic("must specify at least one file") } ledgerFiles, err := ledger.NewFiles(files[0], files[1:]...) if err != nil { panic(err) } positional := fs.Args() if len(positional) == 0 || len(positional[0]) < 3 { panic("positional arguments required, ie bal|reg PATTERN MATCHING") } cmd := positional[0] q, err := BuildQuery(config, positional[1:]) if err != nil { panic(err) } deltas, err := ledgerFiles.Deltas() if err != nil { panic(err) } if period := config.Query.Period; !period.Empty() { after := period.Start.Format("2006-01-02") before := period.Stop.Format("2006-01-02") deltas = deltas.Like( ledger.LikeAfter(after), ledger.LikeBefore(before), ) } pattern := ".*" if depth := config.Query.Depth; depth > 0 { pattern = "" for i := 0; i < depth; i++ { pattern += "[^:]*:" } pattern = strings.Trim(pattern, ":") } group := ledger.GroupName(pattern) bpis := make(ledger.BPIs) if config.BPI != "" { b, err := ledger.NewBPIs(config.BPI) if err != nil { panic(err) } bpis = b } if config.Query.Normalize { deltas = ana.NewDefaultNormalizer().Normalize(deltas) } if config.Query.With != "" { deltas = deltas.Like(ledger.LikeWith(config.Query.With)) } switch cmd[:3] { case "bal": balances := deltas.Balances(). WithBPIs(bpis). KindaLike(q). KindaGroup(group). Nonzero() FPrintBalances(os.Stdout, "", balances, nil, config.Query.USDOnly, config.Query.Normalize, time.Now().Format("2006-01-02")) case "reg": transactions := deltas.Transactions() for i, transaction := range transactions { balances := ledger.Deltas(transaction).Like(q).Group(group).Balances().WithBPIsAt(bpis, transaction[0].Date).Nonzero() shouldPrint := false shouldPrint = shouldPrint || len(balances) > 2 if config.Query.NoExchanging { shouldPrint = shouldPrint || len(balances) > 1 for _, v := range balances { shouldPrint = shouldPrint || len(v) == 1 } } else { shouldPrint = shouldPrint || len(balances) > 0 } if shouldPrint { fmt.Printf("%s\t%s\n", transaction[0].Date, transaction[0].Description) FPrintBalances(os.Stdout, "\t\t", balances, transactions[:i+1].Deltas().Like(q).Group(group).Balances().WithBPIsAt(bpis, transaction[0].Date).Nonzero(), config.Query.USDOnly, config.Query.Normalize, transaction[0].Date) } } default: panic("unknown command " + positional[0]) } } func FPrintBalances(w io.Writer, linePrefix string, balances, cumulatives ledger.Balances, usdOnly, normalized bool, date string) { maxes := map[ledger.Currency]float64{} keys := []string{} for k, v := range balances { keys = append(keys, k) for k2, v2 := range v { if math.Abs(v2) > math.Abs(maxes[k2]) { maxes[k2] = math.Abs(v2) } } } slices.Sort(keys) max := 0 for _, k := range keys { if n := len(k); n > max { max = n } } normalizer := ana.NewDefaultNormalizer() format := fmt.Sprintf("%s%%-%ds\t%%s%%.2f (%%s%%.2f)\n", linePrefix, max) if normalized { format = fmt.Sprintf("%s%%-%ds\t%%s%%.2f (%%s%%.2f (%%.2f @%%.2f (%%s%%.0f)))\n", linePrefix, max) } for _, key := range keys { currencies := []ledger.Currency{} for currency := range balances[key] { currencies = append(currencies, currency) } slices.Sort(currencies) for _, currency := range currencies { printableCurrency := currency if printableCurrency != "$" { printableCurrency += " " } if usdOnly && printableCurrency != "$" { continue } cumulative := balances[key][currency] if balance, ok := cumulatives[key]; !ok { } else if value, ok := balance[currency]; !ok { } else { cumulative = value } if !normalized { fmt.Fprintf(w, format, key, printableCurrency, balances[key][currency], printableCurrency, cumulative) } else { factor := normalizer.NormalizeFactor(ledger.Delta{Name: key, Date: date}) trailingMax := maxes[currency] - math.Abs(balances[key][currency]) fmt.Fprintf(w, format, key, printableCurrency, balances[key][currency], printableCurrency, cumulative, cumulative*factor, factor, printableCurrency, factor*trailingMax) } } } }