This repository has been archived on 2023-12-27. You can view files and clone it, but cannot push or open issues or pull requests.
ledger-tui/pkg/ledger/hledger/hledger.go

159 lines
4.5 KiB
Go
Raw Permalink Normal View History

package hledger
import (
"bytes"
"encoding/json"
"errors"
"os/exec"
"github.com/tgrosinger/ledger-tui/pkg/transaction"
tx "github.com/tgrosinger/ledger-tui/pkg/transaction"
)
// HLedger implements the Ledger interface. It uses the hledger binary to pull
// data from the ledger file.
type HLedger struct {
filepath string
}
func New(filepath string) HLedger {
return HLedger{
filepath: filepath,
}
}
// GetTransactions returns a slice of transactions loaded from the configured
// ledger file.
func (hl HLedger) GetTransactions() ([]tx.Transaction, error) {
out, err := hl.exec("print", "-O", "json")
if err != nil {
return nil, err
}
var results []Transaction
err = json.Unmarshal([]byte(out), &results)
if err != nil {
return nil, err
}
converted, errors := hl.convertTransactions(results)
return converted, transaction.TransactionParseErrors{
Errors: errors,
}
}
func (hl HLedger) AddTransaction(newTx tx.Transaction) error {
return errors.New("Method not implemented")
}
// exec runs an hledger command against the configured ledger file.
func (hl HLedger) exec(args ...string) (string, error) {
allArgs := make([]string, 0, len(args)+2)
allArgs = append(allArgs, "-f", hl.filepath)
allArgs = append(allArgs, args...)
cmd := exec.Command("hledger", allArgs...)
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return "", err
}
return out.String(), nil
}
// convertTransaction converts the transactions as loaded from hledger into a
// friendlier and simplified format used by this application.
func (hl HLedger) convertTransactions(txs []Transaction) ([]transaction.Transaction, []transaction.TransactionParseError) {
output := make([]transaction.Transaction, 0, len(txs))
errors := make([]transaction.TransactionParseError, 0)
for _, tx := range txs {
converted, err := tx.convert()
if err != nil {
position := -1
if len(tx.Tsourcepos) > 0 {
position = tx.Tsourcepos[0].SourceLine
}
errors = append(errors, transaction.TransactionParseError{
Err: err,
Date: tx.Tdate,
Description: tx.Tdescription,
Line: position,
})
} else {
output = append(output, converted)
}
}
return output, errors
}
// Transaction is the data format for transactions as they come from hledger
// when executing `hledger print -O json`.
type Transaction struct {
Tcode string `json:"tcode"`
Tcomment string `json:"tcomment"`
Tdate string `json:"tdate"`
Tdate2 string `json:"tdate2"`
Tdescription string `json:"tdescription"`
Tindex int `json:"tindex"`
Tpostings []struct {
Paccount string `json:"paccount"`
Pamount []struct {
Acommodity string `json:"acommodity"`
Aprice interface{} `json:"aprice"`
Aquantity struct {
DecimalMantissa int `json:"decimalMantissa"`
DecimalPlaces int `json:"decimalPlaces"`
FloatingPoint float64 `json:"floatingPoint"`
} `json:"aquantity"`
Astyle struct {
Ascommodityside string `json:"ascommodityside"`
Ascommodityspaced bool `json:"ascommodityspaced"`
Asdecimalpoint string `json:"asdecimalpoint"`
Asdigitgroups interface{} `json:"asdigitgroups"`
Asprecision int `json:"asprecision"`
} `json:"astyle"`
} `json:"pamount"`
Pbalanceassertion interface{} `json:"pbalanceassertion"`
Pcomment string `json:"pcomment"`
Pdate interface{} `json:"pdate"`
Pdate2 interface{} `json:"pdate2"`
Poriginal interface{} `json:"poriginal"`
Pstatus string `json:"pstatus"`
Ptags []string `json:"ptags"`
Ptransaction string `json:"ptransaction_"`
Ptype string `json:"ptype"`
} `json:"tpostings"`
Tprecedingcomment string `json:"tprecedingcomment"`
Tsourcepos []struct {
SourceColumn int `json:"sourceColumn"`
SourceLine int `json:"sourceLine"`
SourceName string `json:"sourceName"`
} `json:"tsourcepos"`
Tstatus string `json:"tstatus"`
Ttags []string `json:"ttags"`
}
func (t *Transaction) convert() (transaction.Transaction, error) {
tx := transaction.Transaction{
Index: t.Tindex,
Date: t.Tdate,
Description: t.Tdescription,
Comment: t.Tcomment,
PreceedingComment: t.Tprecedingcomment,
Postings: make([]transaction.Posting, len(t.Tpostings)),
Status: t.Tstatus,
Tags: t.Ttags,
}
for _, posting := range t.Tpostings {
tx.Postings = append(tx.Postings, posting.convert())
}
return tx, nil
}