Skip to content

Commit

Permalink
perf: use different strings funcs, remember date layout
Browse files Browse the repository at this point in the history
  • Loading branch information
howeyc committed Feb 2, 2022
1 parent 893e870 commit a38f253
Showing 1 changed file with 37 additions and 38 deletions.
75 changes: 37 additions & 38 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@ import (
"math/big"
"regexp"
"strings"
"time"
"unicode"

date "github.com/joyt/godate"
"github.com/marcmak/calc/calc"
)

const (
whitespace = " \t"
)

// ParseLedger parses a ledger file and returns a list of Transactions.
//
// Transactions are sorted by date.
Expand Down Expand Up @@ -55,7 +53,8 @@ func ParseLedgerAsync(ledgerReader io.Reader) (c chan *Transaction, e chan error
return c, e
}

var accountToAmountSpace = regexp.MustCompile(" {2,}|\t+")
// Calculation expressions are enclosed in parantheses
var calcExpr = regexp.MustCompile(`(?s) \((.*)\)`)

func parseLedger(ledgerReader io.Reader, callback func(t *Transaction, err error) (stop bool)) {
scanner := bufio.NewScanner(ledgerReader)
Expand All @@ -64,6 +63,9 @@ func parseLedger(ledgerReader io.Reader, callback func(t *Transaction, err error
var lineCount int
var comments []string

// default date layout
dateLayout := "2006/01/02"

errorMsg := func(msg string) (stop bool) {
return callback(nil, fmt.Errorf("%s:%d: %s", filename, lineCount, msg))
}
Expand All @@ -78,7 +80,7 @@ func parseLedger(ledgerReader io.Reader, callback func(t *Transaction, err error
}

// remove heading and tailing space from the line
trimmedLine := strings.Trim(line, whitespace)
trimmedLine := strings.TrimSpace(line)
lineCount++

// handle comments
Expand Down Expand Up @@ -108,7 +110,8 @@ func parseLedger(ledgerReader io.Reader, callback func(t *Transaction, err error
_, lines, _ := parseAccount(scanner)
lineCount += lines
default:
trans, lines, transErr := parseTransaction(scanner)
trans, lines, layout, transErr := parseTransaction(dateLayout, scanner)
dateLayout = layout
lineCount += lines
if transErr != nil {
if errorMsg(fmt.Errorf("Unable to parse transaction: %w", transErr).Error()) {
Expand All @@ -126,7 +129,7 @@ func parseLedger(ledgerReader io.Reader, callback func(t *Transaction, err error
func parseAccount(scanner *bufio.Scanner) (accountName string, lines int, err error) {
line := scanner.Text()
// remove heading and tailing space from the line
trimmedLine := strings.Trim(line, whitespace)
trimmedLine := strings.TrimSpace(line)

lineSplit := strings.SplitN(trimmedLine, " ", 2)
if len(lineSplit) != 2 {
Expand All @@ -139,7 +142,7 @@ func parseAccount(scanner *bufio.Scanner) (accountName string, lines int, err er
// Read until blank line (ignore all sub-directives)
line = scanner.Text()
// remove heading and tailing space from the line
trimmedLine = strings.Trim(line, whitespace)
trimmedLine = strings.TrimSpace(line)
lines++

// skip comments
Expand All @@ -156,16 +159,16 @@ func parseAccount(scanner *bufio.Scanner) (accountName string, lines int, err er
return
}

func parseTransaction(scanner *bufio.Scanner) (trans *Transaction, lines int, err error) {
func parseTransaction(currentDateLayout string, scanner *bufio.Scanner) (trans *Transaction, lines int, layout string, err error) {
var comments []string

line := scanner.Text()
trimmedLine := strings.Trim(line, whitespace)
trimmedLine := strings.TrimSpace(line)
// handle comments (comment saved in calling function)
if commentIdx := strings.Index(trimmedLine, ";"); commentIdx >= 0 {
trimmedLine = trimmedLine[:commentIdx]
}
trimmedLine = strings.Trim(trimmedLine, whitespace)
trimmedLine = strings.TrimSpace(trimmedLine)

// Parse Date-Payee line
lineSplit := strings.SplitN(trimmedLine, " ", 2)
Expand All @@ -174,7 +177,14 @@ func parseTransaction(scanner *bufio.Scanner) (trans *Transaction, lines int, er
return
}
dateString := lineSplit[0]
transDate, dateErr := date.Parse(dateString)

// attempt currentDateLayout, hopefully file is consistent
layout = currentDateLayout
transDate, dateErr := time.Parse(layout, dateString)
if dateErr != nil {
// try to find new date layout
transDate, layout, dateErr = date.ParseAndGetLayout(dateString)
}
if dateErr != nil {
err = fmt.Errorf("Unable to parse date: %s", dateString)
return
Expand All @@ -185,7 +195,7 @@ func parseTransaction(scanner *bufio.Scanner) (trans *Transaction, lines int, er
for scanner.Scan() {
line = scanner.Text()
// remove heading and tailing space from the line
trimmedLine = strings.Trim(line, whitespace)
trimmedLine = strings.TrimSpace(line)
lines++

// handle comments
Expand All @@ -195,6 +205,7 @@ func parseTransaction(scanner *bufio.Scanner) (trans *Transaction, lines int, er
if len(trimmedLine) == 0 {
continue
}
trimmedLine = strings.TrimSpace(trimmedLine)
}

if len(trimmedLine) == 0 {
Expand All @@ -208,23 +219,21 @@ func parseTransaction(scanner *bufio.Scanner) (trans *Transaction, lines int, er
return
}
} else {
// Check for expr
trimmedLine = calcExpr.ReplaceAllStringFunc(trimmedLine, func(s string) string {
return fmt.Sprintf("%f", calc.Solve(s))
})

var accChange Account
lineSplit := accountToAmountSpace.Split(trimmedLine, -1)
var nonEmptyWords []string
for _, word := range lineSplit {
if len(word) > 0 {
nonEmptyWords = append(nonEmptyWords, word)
accChange.Name = trimmedLine
if i := strings.LastIndexFunc(trimmedLine, unicode.IsSpace); i >= 0 {
acc := strings.TrimSpace(trimmedLine[:i])
amt := trimmedLine[i+1:]
if ratbal, valid := new(big.Rat).SetString(amt); valid {
accChange.Name = acc
accChange.Balance = ratbal
}
}
lastIndex := len(nonEmptyWords) - 1
balErr, rationalNum := getBalance(strings.Trim(nonEmptyWords[lastIndex], whitespace))
if !balErr {
// Assuming no balance and whole line is account name
accChange.Name = strings.Join(nonEmptyWords, " ")
} else {
accChange.Name = strings.Join(nonEmptyWords[:lastIndex], " ")
accChange.Balance = rationalNum
}
trans.AccountChanges = append(trans.AccountChanges, accChange)
}
}
Expand All @@ -241,16 +250,6 @@ func parseTransaction(scanner *bufio.Scanner) (trans *Transaction, lines int, er
return
}

func getBalance(balance string) (bool, *big.Rat) {
rationalNum := new(big.Rat)
if strings.Contains(balance, "(") {
rationalNum.SetFloat64(calc.Solve(balance))
return true, rationalNum
}
_, isValid := rationalNum.SetString(balance)
return isValid, rationalNum
}

// Takes a transaction and balances it. This is mainly to fill in the empty part
// with the remaining balance.
func balanceTransaction(input *Transaction) error {
Expand Down

0 comments on commit a38f253

Please sign in to comment.