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 }