Skip to content

Commit

Permalink
perf: handle include directives in parser
Browse files Browse the repository at this point in the history
no longer necessary to read the file twice
  • Loading branch information
howeyc committed Mar 26, 2022
1 parent b1912ca commit 0704da4
Show file tree
Hide file tree
Showing 19 changed files with 102 additions and 308 deletions.
35 changes: 35 additions & 0 deletions include_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package ledger

import (
"errors"
"testing"
)

func TestIncludeSimple(t *testing.T) {
trans, err := ParseLedgerFile("testdata/ledgerRoot.dat")
if err != nil {
t.Fatal(err)
}
bals := GetBalances(trans, []string{"Assets"})
if bals[0].Balance.StringRound() != "50" {
t.Fatal(errors.New("should be 50"))
}
}

func TestIncludeGlob(t *testing.T) {
trans, err := ParseLedgerFile("testdata/ledgerRootGlob.dat")
if err != nil {
t.Fatal(err)
}
bals := GetBalances(trans, []string{"Assets"})
if bals[0].Balance.StringRound() != "80" {
t.Fatal(errors.New("should be 80"))
}
}

func TestIncludeUnbalanced(t *testing.T) {
_, err := ParseLedgerFile("testdata/ledgerRootUnbalanced.dat")
if err.Error() != "testdata/ledger-2021-05.dat:12: Unable to parse transaction: Unable to balance transaction: no empty account to place extra balance" {
t.Fatal(err)
}
}
8 changes: 1 addition & 7 deletions ledger/cmd/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,7 @@ var importCmd = &cobra.Command{
}
defer csvFileReader.Close()

ledgerFileReader, err := ledger.NewLedgerReader(ledgerFilePath)
if err != nil {
fmt.Println("Ledger: ", err)
return
}

generalLedger, parseError := ledger.ParseLedger(ledgerFileReader)
generalLedger, parseError := ledger.ParseLedgerFile(ledgerFilePath)
if parseError != nil {
fmt.Printf("%s:%s\n", ledgerFilePath, parseError.Error())
return
Expand Down
24 changes: 3 additions & 21 deletions ledger/cmd/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cmd

import (
"fmt"
"os"

"github.com/howeyc/ledger"
"github.com/spf13/cobra"
Expand All @@ -13,27 +12,10 @@ var lintCmd = &cobra.Command{
Use: "lint",
Short: "Check ledger for errors",
Run: func(cmd *cobra.Command, args []string) {
ledgerFileReader, err := ledger.NewLedgerReader(ledgerFilePath)
if err != nil {
fmt.Println("Ledger: ", err)
return
_, lerr := ledger.ParseLedgerFile(ledgerFilePath)
if lerr != nil {
fmt.Println("Ledger: ", lerr)
}

c, e := ledger.ParseLedgerAsync(ledgerFileReader)
errorCount := 0
for {
select {
case <-c:
continue
case err := <-e:
if err == nil {
os.Exit(errorCount)
}
fmt.Println("Ledger: ", err)
errorCount++
}
}

},
}

Expand Down
14 changes: 4 additions & 10 deletions ledger/cmd/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,13 @@ func cliTransactions() ([]*ledger.Transaction, error) {
return nil, errors.New("unable to parse start or end date string argument")
}

var lreader io.Reader

var generalLedger []*ledger.Transaction
var parseError error
if ledgerFilePath == "-" {
lreader = os.Stdin
generalLedger, parseError = ledger.ParseLedger(os.Stdin)
} else {
ledgerFileReader, err := ledger.NewLedgerReader(ledgerFilePath)
if err != nil {
return nil, err
}
lreader = ledgerFileReader
generalLedger, parseError = ledger.ParseLedgerFile(ledgerFilePath)
}

generalLedger, parseError := ledger.ParseLedger(lreader)
if parseError != nil {
return nil, parseError
}
Expand Down
6 changes: 1 addition & 5 deletions ledger/cmd/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@ var statsCmd = &cobra.Command{
Use: "stats",
Short: "A small report of transaction stats",
Run: func(cmd *cobra.Command, args []string) {
lreader, err := ledger.NewLedgerReader(ledgerFilePath)
if err != nil {
log.Fatalln(err)
}
transactions, terr := ledger.ParseLedger(lreader)
transactions, terr := ledger.ParseLedgerFile(ledgerFilePath)
if terr != nil {
log.Fatalln(terr)
}
Expand Down
33 changes: 1 addition & 32 deletions ledger/cmd/web.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
package cmd

import (
"bytes"
"crypto/sha256"
"embed"
"fmt"
"io"
"log"
"net/http"
"sync"
"time"

"github.com/howeyc/ledger/ledger/cmd/internal/httpcompress"
Expand All @@ -21,9 +17,6 @@ import (
var reportConfigFileName string
var stockConfigFileName string
var quickviewConfigFileName string
var ledgerLock sync.Mutex
var currentSum []byte
var currentTrans []*ledger.Transaction

var serverPort int
var localhost bool
Expand All @@ -35,34 +28,10 @@ var contentStatic embed.FS
var contentTemplates embed.FS

func getTransactions() ([]*ledger.Transaction, error) {
ledgerLock.Lock()
defer ledgerLock.Unlock()

var buf bytes.Buffer
h := sha256.New()

ledgerFileReader, err := ledger.NewLedgerReader(ledgerFilePath)
if err != nil {
return nil, err
}
tr := io.TeeReader(ledgerFileReader, h)
_, err = io.Copy(&buf, tr)
if err != nil {
return nil, err
}

sum := h.Sum(nil)
if bytes.Equal(currentSum, sum) {
return currentTrans, nil
}

trans, terr := ledger.ParseLedger(&buf)
trans, terr := ledger.ParseLedgerFile(ledgerFilePath)
if terr != nil {
return nil, fmt.Errorf("%s", terr.Error())
}
currentSum = sum
currentTrans = trans

return trans, nil
}

Expand Down
90 changes: 10 additions & 80 deletions ledgerReader.go
Original file line number Diff line number Diff line change
@@ -1,94 +1,24 @@
package ledger

import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
)

const (
markerPrefix = ";__ledger_file"
markerSep = "*-*"
)

var includedFiles = make(map[string]bool)

// NewLedgerReader reads a file and includes any files with include directives
// and returns the whole combined ledger as a buffer for parsing.
func NewLedgerReader(filename string) (*bytes.Buffer, error) {
//
// Deprecated: use ParseLedgerFile
func NewLedgerReader(filename string) (io.Reader, error) {
var buf bytes.Buffer

err := includeFile(filename, &buf)
return &buf, err
}

// includeFile reads filename into buf, adding special marker comments
// when there are step changes in file location due to 'include' directive.
func includeFile(filename string, buf *bytes.Buffer) error {
if filename == "" {
return errors.New("must specify filename")
}
filename = filepath.Clean(filename)
lineNum := 0

// check for include cyles
if includedFiles[filename] {
return fmt.Errorf("include cycle: '%s'", filename)
}
includedFiles[filename] = true

defer delete(includedFiles, filename)

f, err := os.Open(filename)
if err != nil {
return err
ifile, ierr := os.Open(filename)
if ierr != nil {
return &buf, ierr
}
defer f.Close()
s := bufio.NewScanner(f)

// mark the start of this file
fmt.Fprintln(buf, marker(filename, lineNum))

for s.Scan() {
line := s.Text()

if prefix, incname, found := strings.Cut(line, " "); found && prefix == "include" {
// Resolve filepaths
includedPath := filepath.Join(filename, "..", incname)
includedPaths, err := filepath.Glob(includedPath)

// Include all resolved filepaths
for i := 0; i < len(includedPaths) && err == nil; i++ {
if !includedFiles[includedPaths[i]] {
err = includeFile(includedPaths[i], buf)
}
}
if err != nil {
return fmt.Errorf("%s:%d: %s", filename, lineNum, err.Error())
}
lineNum++

// mark the resumption point for this file
fmt.Fprintln(buf, marker(filename, lineNum))
} else {
fmt.Fprintln(buf, s.Text())
lineNum++
}
}
return nil
}

func marker(filename string, lineNum int) string {
return strings.Join([]string{markerPrefix, filename, strconv.Itoa(lineNum)}, markerSep)
}
io.Copy(&buf, ifile)
ifile.Close()

func parseMarker(s string) (string, int) {
v := strings.Split(s, markerSep)
lineNum, _ := strconv.Atoi(v[2])
return v[1], lineNum
return &buf, nil
}
81 changes: 0 additions & 81 deletions ledgerReader_test.go

This file was deleted.

Loading

0 comments on commit 0704da4

Please sign in to comment.