130 lines
2.8 KiB
Go
Executable File
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
|
|
}
|