134 lines
2.9 KiB
Go
Executable File
134 lines
2.9 KiB
Go
Executable File
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type Transaction struct {
|
|
Date string
|
|
Description string
|
|
Payer string
|
|
Payee string
|
|
Amount float32
|
|
}
|
|
|
|
func readTransaction(r io.Reader) (Transaction, error) {
|
|
lines := make([][]byte, 3)
|
|
for i := range lines {
|
|
line, err := nextLine(r)
|
|
if err != nil {
|
|
return Transaction{}, err
|
|
}
|
|
lines[i] = line
|
|
}
|
|
for i := range lines {
|
|
if len(lines[i]) == 0 {
|
|
return Transaction{}, fmt.Errorf("stub transaction: line %d empty: %q, %q, %q", i, lines[0], lines[1], lines[2])
|
|
}
|
|
}
|
|
var transaction Transaction
|
|
for _, foo := range []func([][]byte) error{
|
|
transaction.readDate,
|
|
transaction.readDescription,
|
|
transaction.readAmount,
|
|
transaction.readPayerPayee,
|
|
} {
|
|
if err := foo(lines); err != nil {
|
|
return Transaction{}, err
|
|
}
|
|
}
|
|
return transaction, nil
|
|
}
|
|
|
|
func nextLine(r io.Reader) ([]byte, error) {
|
|
line, err := readLine(r)
|
|
for err == nil && len(bytes.TrimSpace(line)) == 0 {
|
|
line, err = readLine(r)
|
|
}
|
|
return line, err
|
|
}
|
|
|
|
func readLine(r io.Reader) ([]byte, error) {
|
|
w := bytes.NewBuffer(make([]byte, 0, 128))
|
|
buff := make([]byte, 1)
|
|
n, err := r.Read(buff)
|
|
for err == nil && n > 0 && buff[0] != '\n' {
|
|
w.Write(buff)
|
|
n, err = r.Read(buff)
|
|
}
|
|
return w.Bytes(), err
|
|
}
|
|
|
|
func words(b []byte) [][]byte {
|
|
scanner := bufio.NewScanner(bytes.NewReader(b))
|
|
scanner.Split(bufio.ScanWords)
|
|
words := make([][]byte, 0)
|
|
for scanner.Scan() {
|
|
words = append(words, []byte(scanner.Text()))
|
|
}
|
|
return words
|
|
}
|
|
|
|
func (transaction *Transaction) readDate(lines [][]byte) error {
|
|
if len(lines) < 1 || len(words(lines[0])) < 1 {
|
|
return errors.New("stub")
|
|
}
|
|
transaction.Date = string(words(lines[0])[0])
|
|
return nil
|
|
}
|
|
|
|
func (transaction *Transaction) readDescription(lines [][]byte) error {
|
|
if len(lines) < 1 || len(words(lines[0])) < 1 {
|
|
return errors.New("stub")
|
|
}
|
|
transaction.Description = string(bytes.Join(words(lines[0])[1:], []byte(" ")))
|
|
return nil
|
|
}
|
|
|
|
func (transaction *Transaction) readAmount(lines [][]byte) error {
|
|
if len(lines) < 2 || len(words(lines[1])) < 2 {
|
|
return errors.New("stub")
|
|
}
|
|
amount := string(words(lines[1])[1])
|
|
f, err := strconv.ParseFloat(strings.Trim(amount, "$"), 32)
|
|
transaction.Amount = float32(f)
|
|
return err
|
|
}
|
|
|
|
func (transaction *Transaction) readPayerPayee(lines [][]byte) error {
|
|
if len(lines) < 3 || len(words(lines[1])) < 1 || len(words(lines[2])) < 1 {
|
|
return errors.New("stub")
|
|
}
|
|
payer := string(words(lines[1])[0])
|
|
payee := string(words(lines[2])[0])
|
|
if transaction.Amount >= 0 {
|
|
tmp := payer
|
|
payer = payee
|
|
payee = tmp
|
|
} else {
|
|
transaction.Amount *= -1
|
|
}
|
|
transaction.Payer = payer
|
|
transaction.Payee = payee
|
|
return nil
|
|
}
|
|
|
|
func (transaction Transaction) Marshal() string {
|
|
return fmt.Sprintf(
|
|
"%-25s%s\n%25s%-49s $%.2f\n%25s%s",
|
|
transaction.Date,
|
|
transaction.Description,
|
|
"",
|
|
transaction.Payee,
|
|
transaction.Amount,
|
|
"",
|
|
transaction.Payer,
|
|
)
|
|
}
|