From d5bea1102ddedd1f3e9c3d9438f134384e1706b5 Mon Sep 17 00:00:00 2001 From: Aaron Lindsay Date: Sun, 10 Dec 2017 20:50:37 -0500 Subject: [PATCH] Split Lua reports into own package --- .travis.yml | 4 +- internal/handlers/prices.go | 22 ----- internal/handlers/reports.go | 87 +----------------- internal/handlers/transactions.go | 14 --- .../accounts_lua.go => reports/accounts.go} | 19 +++- .../balance_lua.go => reports/balance.go} | 2 +- .../{handlers/date_lua.go => reports/date.go} | 2 +- .../prices_lua.go => reports/prices.go} | 2 +- internal/reports/reports.go | 88 +++++++++++++++++++ .../securities.go} | 26 +++++- .../reports_lua.go => reports/tabulations.go} | 2 +- 11 files changed, 138 insertions(+), 130 deletions(-) rename internal/{handlers/accounts_lua.go => reports/accounts.go} (93%) rename internal/{handlers/balance_lua.go => reports/balance.go} (99%) rename internal/{handlers/date_lua.go => reports/date.go} (99%) rename internal/{handlers/prices_lua.go => reports/prices.go} (99%) create mode 100644 internal/reports/reports.go rename internal/{handlers/securities_lua.go => reports/securities.go} (87%) rename internal/{handlers/reports_lua.go => reports/tabulations.go} (99%) diff --git a/.travis.yml b/.travis.yml index 6bba735..e4a54dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -59,8 +59,8 @@ script: - touch $GOPATH/src/github.com/aclindsa/moneygo/internal/handlers/cusip_list.csv # Build and test MoneyGo - go generate -v github.com/aclindsa/moneygo/internal/handlers - - go test -v -covermode=count -coverpkg github.com/aclindsa/moneygo/internal/config,github.com/aclindsa/moneygo/internal/handlers,github.com/aclindsa/moneygo/internal/models,github.com/aclindsa/moneygo/internal/store,github.com/aclindsa/moneygo/internal/store/db -coverprofile=integration_coverage.out github.com/aclindsa/moneygo/internal/integration - - go test -v -covermode=count -coverpkg github.com/aclindsa/moneygo/internal/config,github.com/aclindsa/moneygo/internal/handlers,github.com/aclindsa/moneygo/internal/models,github.com/aclindsa/moneygo/internal/store,github.com/aclindsa/moneygo/internal/store/db -coverprofile=config_coverage.out github.com/aclindsa/moneygo/internal/config + - go test -v -covermode=count -coverpkg github.com/aclindsa/moneygo/internal/config,github.com/aclindsa/moneygo/internal/handlers,github.com/aclindsa/moneygo/internal/models,github.com/aclindsa/moneygo/internal/store,github.com/aclindsa/moneygo/internal/store/db,github.com/aclindsa/moneygo/internal/reports -coverprofile=integration_coverage.out github.com/aclindsa/moneygo/internal/integration + - go test -v -covermode=count -coverpkg github.com/aclindsa/moneygo/internal/config,github.com/aclindsa/moneygo/internal/handlers,github.com/aclindsa/moneygo/internal/models,github.com/aclindsa/moneygo/internal/store,github.com/aclindsa/moneygo/internal/store/db,github.com/aclindsa/moneygo/internal/reports -coverprofile=config_coverage.out github.com/aclindsa/moneygo/internal/config # Report the test coverage after_script: diff --git a/internal/handlers/prices.go b/internal/handlers/prices.go index f737df1..81e6ccc 100644 --- a/internal/handlers/prices.go +++ b/internal/handlers/prices.go @@ -5,7 +5,6 @@ import ( "github.com/aclindsa/moneygo/internal/store" "log" "net/http" - "time" ) func CreatePriceIfNotExist(tx store.Tx, price *models.Price) error { @@ -33,27 +32,6 @@ func CreatePriceIfNotExist(tx store.Tx, price *models.Price) error { return nil } -// Return the price for security in currency closest to date -func GetClosestPrice(tx store.Tx, security, currency *models.Security, date *time.Time) (*models.Price, error) { - earliest, _ := tx.GetEarliestPrice(security, currency, date) - latest, err := tx.GetLatestPrice(security, currency, date) - - // Return early if either earliest or latest are invalid - if earliest == nil { - return latest, err - } else if err != nil { - return earliest, nil - } - - howlate := earliest.Date.Sub(*date) - howearly := date.Sub(latest.Date) - if howearly < howlate { - return latest, nil - } else { - return earliest, nil - } -} - func PriceHandler(r *http.Request, context *Context, user *models.User, securityid int64) ResponseWriterWriter { security, err := context.Tx.GetSecurity(securityid, user.UserId) if err != nil { diff --git a/internal/handlers/reports.go b/internal/handlers/reports.go index abf554f..194d3a4 100644 --- a/internal/handlers/reports.go +++ b/internal/handlers/reports.go @@ -1,104 +1,23 @@ package handlers import ( - "context" - "errors" - "fmt" "github.com/aclindsa/moneygo/internal/models" + "github.com/aclindsa/moneygo/internal/reports" "github.com/aclindsa/moneygo/internal/store" - "github.com/yuin/gopher-lua" "log" "net/http" - "time" ) -//type and value to store user in lua's Context -type key int - -const ( - userContextKey key = iota - accountsContextKey - securitiesContextKey - balanceContextKey - dbContextKey -) - -const luaTimeoutSeconds time.Duration = 30 // maximum time a lua request can run for - -func runReport(tx store.Tx, user *models.User, report *models.Report) (*models.Tabulation, error) { - // Create a new LState without opening the default libs for security - L := lua.NewState(lua.Options{SkipOpenLibs: true}) - defer L.Close() - - // Create a new context holding the current user with a timeout - ctx := context.WithValue(context.Background(), userContextKey, user) - ctx = context.WithValue(ctx, dbContextKey, tx) - ctx, cancel := context.WithTimeout(ctx, luaTimeoutSeconds*time.Second) - defer cancel() - L.SetContext(ctx) - - for _, pair := range []struct { - n string - f lua.LGFunction - }{ - {lua.LoadLibName, lua.OpenPackage}, // Must be first - {lua.BaseLibName, lua.OpenBase}, - {lua.TabLibName, lua.OpenTable}, - {lua.StringLibName, lua.OpenString}, - {lua.MathLibName, lua.OpenMath}, - } { - if err := L.CallByParam(lua.P{ - Fn: L.NewFunction(pair.f), - NRet: 0, - Protect: true, - }, lua.LString(pair.n)); err != nil { - return nil, errors.New("Error initializing Lua packages") - } - } - - luaRegisterAccounts(L) - luaRegisterSecurities(L) - luaRegisterBalances(L) - luaRegisterDates(L) - luaRegisterTabulations(L) - luaRegisterPrices(L) - - err := L.DoString(report.Lua) - - if err != nil { - return nil, err - } - - if err := L.CallByParam(lua.P{ - Fn: L.GetGlobal("generate"), - NRet: 1, - Protect: true, - }); err != nil { - return nil, err - } - - value := L.Get(-1) - if ud, ok := value.(*lua.LUserData); ok { - if tabulation, ok := ud.Value.(*models.Tabulation); ok { - return tabulation, nil - } else { - return nil, fmt.Errorf("generate() for %s (Id: %d) didn't return a tabulation", report.Name, report.ReportId) - } - } else { - return nil, fmt.Errorf("generate() for %s (Id: %d) didn't even return LUserData", report.Name, report.ReportId) - } -} - func ReportTabulationHandler(tx store.Tx, r *http.Request, user *models.User, reportid int64) ResponseWriterWriter { report, err := tx.GetReport(reportid, user.UserId) if err != nil { return NewError(3 /*Invalid Request*/) } - tabulation, err := runReport(tx, user, report) + tabulation, err := reports.RunReport(tx, user, report) if err != nil { // TODO handle different failure cases differently - log.Print("runReport returned:", err) + log.Print("reports.RunReport returned:", err) return NewError(3 /*Invalid Request*/) } diff --git a/internal/handlers/transactions.go b/internal/handlers/transactions.go index 1d522d0..8dd6557 100644 --- a/internal/handlers/transactions.go +++ b/internal/handlers/transactions.go @@ -182,20 +182,6 @@ func TransactionHandler(r *http.Request, context *Context) ResponseWriterWriter return NewError(3 /*Invalid Request*/) } -func BalanceFromSplits(splits *[]*models.Split) (*big.Rat, error) { - var balance, tmp big.Rat - for _, s := range *splits { - rat_amount, err := models.GetBigAmount(s.Amount) - if err != nil { - return nil, err - } - tmp.Add(&balance, rat_amount) - balance.Set(&tmp) - } - - return &balance, nil -} - // Return only those transactions which have at least one split pertaining to // an account func AccountTransactionsHandler(context *Context, r *http.Request, user *models.User, accountid int64) ResponseWriterWriter { diff --git a/internal/handlers/accounts_lua.go b/internal/reports/accounts.go similarity index 93% rename from internal/handlers/accounts_lua.go rename to internal/reports/accounts.go index 6d135a6..d495c06 100644 --- a/internal/handlers/accounts_lua.go +++ b/internal/reports/accounts.go @@ -1,4 +1,4 @@ -package handlers +package reports import ( "context" @@ -6,6 +6,7 @@ import ( "github.com/aclindsa/moneygo/internal/models" "github.com/aclindsa/moneygo/internal/store" "github.com/yuin/gopher-lua" + "math/big" "strings" ) @@ -146,6 +147,20 @@ func luaAccount__index(L *lua.LState) int { return 1 } +func balanceFromSplits(splits *[]*models.Split) (*big.Rat, error) { + var balance, tmp big.Rat + for _, s := range *splits { + rat_amount, err := models.GetBigAmount(s.Amount) + if err != nil { + return nil, err + } + tmp.Add(&balance, rat_amount) + balance.Set(&tmp) + } + + return &balance, nil +} + func luaAccountBalance(L *lua.LState) int { a := luaCheckAccount(L, 1) @@ -181,7 +196,7 @@ func luaAccountBalance(L *lua.LState) int { if err != nil { panic("Failed to fetch splits for account:" + err.Error()) } - rat, err := BalanceFromSplits(splits) + rat, err := balanceFromSplits(splits) if err != nil { panic("Failed to calculate balance for account:" + err.Error()) } diff --git a/internal/handlers/balance_lua.go b/internal/reports/balance.go similarity index 99% rename from internal/handlers/balance_lua.go rename to internal/reports/balance.go index c4d6b63..acd5c43 100644 --- a/internal/handlers/balance_lua.go +++ b/internal/reports/balance.go @@ -1,4 +1,4 @@ -package handlers +package reports import ( "github.com/aclindsa/moneygo/internal/models" diff --git a/internal/handlers/date_lua.go b/internal/reports/date.go similarity index 99% rename from internal/handlers/date_lua.go rename to internal/reports/date.go index a339505..533de2b 100644 --- a/internal/handlers/date_lua.go +++ b/internal/reports/date.go @@ -1,4 +1,4 @@ -package handlers +package reports import ( "github.com/yuin/gopher-lua" diff --git a/internal/handlers/prices_lua.go b/internal/reports/prices.go similarity index 99% rename from internal/handlers/prices_lua.go rename to internal/reports/prices.go index 1ff0da2..862448a 100644 --- a/internal/handlers/prices_lua.go +++ b/internal/reports/prices.go @@ -1,4 +1,4 @@ -package handlers +package reports import ( "github.com/aclindsa/moneygo/internal/models" diff --git a/internal/reports/reports.go b/internal/reports/reports.go new file mode 100644 index 0000000..225a928 --- /dev/null +++ b/internal/reports/reports.go @@ -0,0 +1,88 @@ +package reports + +import ( + "context" + "errors" + "fmt" + "github.com/aclindsa/moneygo/internal/models" + "github.com/aclindsa/moneygo/internal/store" + "github.com/yuin/gopher-lua" + "time" +) + +//type and value to store user in lua's Context +type key int + +const ( + userContextKey key = iota + accountsContextKey + securitiesContextKey + balanceContextKey + dbContextKey +) + +const luaTimeoutSeconds time.Duration = 30 // maximum time a lua request can run for + +func RunReport(tx store.Tx, user *models.User, report *models.Report) (*models.Tabulation, error) { + // Create a new LState without opening the default libs for security + L := lua.NewState(lua.Options{SkipOpenLibs: true}) + defer L.Close() + + // Create a new context holding the current user with a timeout + ctx := context.WithValue(context.Background(), userContextKey, user) + ctx = context.WithValue(ctx, dbContextKey, tx) + ctx, cancel := context.WithTimeout(ctx, luaTimeoutSeconds*time.Second) + defer cancel() + L.SetContext(ctx) + + for _, pair := range []struct { + n string + f lua.LGFunction + }{ + {lua.LoadLibName, lua.OpenPackage}, // Must be first + {lua.BaseLibName, lua.OpenBase}, + {lua.TabLibName, lua.OpenTable}, + {lua.StringLibName, lua.OpenString}, + {lua.MathLibName, lua.OpenMath}, + } { + if err := L.CallByParam(lua.P{ + Fn: L.NewFunction(pair.f), + NRet: 0, + Protect: true, + }, lua.LString(pair.n)); err != nil { + return nil, errors.New("Error initializing Lua packages") + } + } + + luaRegisterAccounts(L) + luaRegisterSecurities(L) + luaRegisterBalances(L) + luaRegisterDates(L) + luaRegisterTabulations(L) + luaRegisterPrices(L) + + err := L.DoString(report.Lua) + + if err != nil { + return nil, err + } + + if err := L.CallByParam(lua.P{ + Fn: L.GetGlobal("generate"), + NRet: 1, + Protect: true, + }); err != nil { + return nil, err + } + + value := L.Get(-1) + if ud, ok := value.(*lua.LUserData); ok { + if tabulation, ok := ud.Value.(*models.Tabulation); ok { + return tabulation, nil + } else { + return nil, fmt.Errorf("generate() for %s (Id: %d) didn't return a tabulation", report.Name, report.ReportId) + } + } else { + return nil, fmt.Errorf("generate() for %s (Id: %d) didn't even return LUserData", report.Name, report.ReportId) + } +} diff --git a/internal/handlers/securities_lua.go b/internal/reports/securities.go similarity index 87% rename from internal/handlers/securities_lua.go rename to internal/reports/securities.go index eaaf71d..de3a215 100644 --- a/internal/handlers/securities_lua.go +++ b/internal/reports/securities.go @@ -1,4 +1,4 @@ -package handlers +package reports import ( "context" @@ -6,6 +6,7 @@ import ( "github.com/aclindsa/moneygo/internal/models" "github.com/aclindsa/moneygo/internal/store" "github.com/yuin/gopher-lua" + "time" ) const luaSecurityTypeName = "security" @@ -153,6 +154,27 @@ func luaSecurity__index(L *lua.LState) int { return 1 } +// Return the price for security in currency closest to date +func getClosestPrice(tx store.Tx, security, currency *models.Security, date *time.Time) (*models.Price, error) { + earliest, _ := tx.GetEarliestPrice(security, currency, date) + latest, err := tx.GetLatestPrice(security, currency, date) + + // Return early if either earliest or latest are invalid + if earliest == nil { + return latest, err + } else if err != nil { + return earliest, nil + } + + howlate := earliest.Date.Sub(*date) + howearly := date.Sub(latest.Date) + if howearly < howlate { + return latest, nil + } else { + return earliest, nil + } +} + func luaClosestPrice(L *lua.LState) int { s := luaCheckSecurity(L, 1) c := luaCheckSecurity(L, 2) @@ -164,7 +186,7 @@ func luaClosestPrice(L *lua.LState) int { panic("Couldn't find tx in lua's Context") } - p, err := GetClosestPrice(tx, s, c, date) + p, err := getClosestPrice(tx, s, c, date) if err != nil { L.Push(lua.LNil) } else { diff --git a/internal/handlers/reports_lua.go b/internal/reports/tabulations.go similarity index 99% rename from internal/handlers/reports_lua.go rename to internal/reports/tabulations.go index 51d919d..fd3d391 100644 --- a/internal/handlers/reports_lua.go +++ b/internal/reports/tabulations.go @@ -1,4 +1,4 @@ -package handlers +package reports import ( "github.com/aclindsa/moneygo/internal/models"