ledger-ui/ledger.go

130 lines
2.8 KiB
Go
Executable File

package main
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"time"
)
type Ledger struct {
path string
}
func NewLedger(path string) (Ledger, error) {
info, err := os.Stat(path)
if err == nil && info.IsDir() {
return Ledger{}, errors.New("path is dir")
}
return Ledger{
path: path,
}, err
}
func (ledger Ledger) NewTransaction() error {
transactions, err := ledger.Transactions()
if err != nil {
return err
}
transactions = append(transactions, Transaction{
Date: time.Now().Format("2006-01-02"),
Description: "?",
Payer: "?",
Payee: "?",
Amount: 0,
})
return ledger.SetTransactions(transactions)
}
func (ledger Ledger) SetTransaction(i int, transaction Transaction) error {
transactions, err := ledger.Transactions()
if err != nil {
return err
}
if i >= len(transactions) {
return errors.New("i too high")
}
transactions[i] = transaction
return ledger.SetTransactions(transactions)
}
func (ledger Ledger) SetTransactions(transactions []Transaction) error {
f, err := ioutil.TempFile(path.Dir(ledger.path), path.Base(ledger.path)+".*")
if err != nil {
return err
}
for _, transaction := range transactions {
if _, err := fmt.Fprintf(f, "%s\n", transaction.Marshal()); err != nil {
return err
}
}
if err := f.Close(); err != nil {
return err
}
return os.Rename(f.Name(), ledger.path)
}
func (ledger Ledger) LossyBalances() (Balances, error) {
return ledger.balances(true)
}
func (ledger Ledger) Balances() (Balances, error) {
return ledger.balances(false)
}
func (ledger Ledger) balances(lossy bool) (Balances, error) {
transactions, err := ledger.Transactions()
if !lossy && err != nil {
return nil, err
}
balances := make(Balances)
for _, transaction := range transactions {
balances.Add(transaction.Payee, transaction.Amount)
balances.Add(transaction.Payer, -1*transaction.Amount)
}
return balances, nil
}
func (ledger Ledger) LossyTransactions() ([]Transaction, error) {
return ledger.transactions(true)
}
func (ledger Ledger) Transactions() ([]Transaction, error) {
return ledger.transactions(false)
}
func (ledger Ledger) transactions(lossy bool) ([]Transaction, error) {
f, err := ledger.open()
if err != nil {
return nil, err
}
defer f.Close()
transactions := make([]Transaction, 0)
for (!lossy && err == nil) || (lossy && err != io.EOF) {
var transaction Transaction
transaction, err = readTransaction(f)
if err == io.EOF {
} else if err == nil {
transactions = append(transactions, transaction)
} else {
err = fmt.Errorf("error parsing transaction #%d: %v", len(transactions), err)
}
}
if err == io.EOF || lossy {
err = nil
}
return transactions, err
}
func (ledger Ledger) open() (io.ReadCloser, error) {
f, err := os.Open(ledger.path)
if os.IsNotExist(err) {
return ioutil.NopCloser(bytes.NewReader([]byte{})), nil
}
return f, err
}