2022-08-04 08:39:37 -07:00
|
|
|
package hledger
|
|
|
|
|
|
|
|
import (
|
2022-12-12 08:26:10 -08:00
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
2022-08-04 08:39:37 -07:00
|
|
|
"errors"
|
2022-12-12 08:26:10 -08:00
|
|
|
"os/exec"
|
2022-08-04 08:39:37 -07:00
|
|
|
|
2022-12-12 08:26:10 -08:00
|
|
|
"github.com/tgrosinger/ledger-tui/pkg/transaction"
|
2022-08-04 08:39:37 -07:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-12-12 08:26:10 -08:00
|
|
|
func New(filepath string) HLedger {
|
|
|
|
return HLedger{
|
|
|
|
filepath: filepath,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetTransactions returns a slice of transactions loaded from the configured
|
|
|
|
// ledger file.
|
2022-08-04 08:39:37 -07:00
|
|
|
func (hl HLedger) GetTransactions() ([]tx.Transaction, error) {
|
2022-12-12 08:26:10 -08:00
|
|
|
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,
|
|
|
|
}
|
2022-08-04 08:39:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (hl HLedger) AddTransaction(newTx tx.Transaction) error {
|
|
|
|
return errors.New("Method not implemented")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-12-12 08:26:10 -08:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2022-08-04 08:39:37 -07:00
|
|
|
// Transaction is the data format for transactions as they come from hledger
|
|
|
|
// when executing `hledger print -O json`.
|
|
|
|
type Transaction struct {
|
2022-12-12 08:26:10 -08:00
|
|
|
Tcode string `json:"tcode"`
|
|
|
|
Tcomment string `json:"tcomment"`
|
|
|
|
Tdate string `json:"tdate"`
|
|
|
|
Tdate2 string `json:"tdate2"`
|
|
|
|
Tdescription string `json:"tdescription"`
|
|
|
|
Tindex int `json:"tindex"`
|
2022-08-04 08:39:37 -07:00
|
|
|
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"`
|
2022-12-12 08:26:10 -08:00
|
|
|
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"`
|
2022-08-04 08:39:37 -07:00
|
|
|
} `json:"tpostings"`
|
|
|
|
Tprecedingcomment string `json:"tprecedingcomment"`
|
|
|
|
Tsourcepos []struct {
|
|
|
|
SourceColumn int `json:"sourceColumn"`
|
|
|
|
SourceLine int `json:"sourceLine"`
|
|
|
|
SourceName string `json:"sourceName"`
|
|
|
|
} `json:"tsourcepos"`
|
2022-12-12 08:26:10 -08:00
|
|
|
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
|
2022-08-04 08:39:37 -07:00
|
|
|
}
|