package main import ( "bufio" "bytes" "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 { transaction.Date = string(words(lines[0])[0]) return nil } func (transaction *Transaction) readDescription(lines [][]byte) error { transaction.Description = string(bytes.Join(words(lines[0])[1:], []byte(" "))) return nil } func (transaction *Transaction) readAmount(lines [][]byte) error { 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 { 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%-50s$%.2f\n%25s%s", transaction.Date, transaction.Description, "", transaction.Payee, transaction.Amount, "", transaction.Payer, ) }