mirror of
https://github.com/Jguer/yay.git
synced 2026-07-22 02:07:27 -04:00
* add render hooks * re-render hook per event * add example with single_line and simplify built rendering * update docs availability * remove comments
671 lines
18 KiB
Go
671 lines
18 KiB
Go
package lua
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
mapset "github.com/deckarep/golang-set/v2"
|
|
glua "github.com/yuin/gopher-lua"
|
|
)
|
|
|
|
const (
|
|
EventAURPreInstall = "AURPreInstall"
|
|
EventAURPostDownload = "AURPostDownload"
|
|
EventUpgradeSelect = "UpgradeSelect"
|
|
EventPostInstall = "PostInstall"
|
|
EventSearchFilter = "SearchFilter"
|
|
EventRenderAUR = "RenderAUR"
|
|
EventRenderSync = "RenderSync"
|
|
)
|
|
|
|
type Autocmd struct {
|
|
Event string
|
|
Desc string
|
|
callback *glua.LFunction
|
|
}
|
|
|
|
type AURPreInstallEvent struct {
|
|
Base string
|
|
Dir string
|
|
PKGBUILDPath string
|
|
SRCINFOPath string
|
|
PKGBUILD string
|
|
Version string
|
|
LastModified int64
|
|
Installed bool
|
|
Packages []AURPreInstallPackage
|
|
SRCINFO AURPreInstallSRCINFO
|
|
}
|
|
|
|
type AURPreInstallPackage struct {
|
|
Name string
|
|
Version string
|
|
LocalVersion string
|
|
Reason string
|
|
Upgrade bool
|
|
Devel bool
|
|
}
|
|
|
|
type AURPreInstallSRCINFO struct {
|
|
Pkgbase string
|
|
Pkgver string
|
|
Pkgrel string
|
|
Epoch string
|
|
Version string
|
|
Pkgdesc string
|
|
URL string
|
|
Arch []string
|
|
License []string
|
|
Depends []string
|
|
MakeDepends []string
|
|
CheckDepends []string
|
|
OptDepends []string
|
|
Provides []string
|
|
Conflicts []string
|
|
Replaces []string
|
|
}
|
|
|
|
type UpgradeSelectEvent struct {
|
|
Upgrades []UpgradeSelectPackage
|
|
PulledDependencies []UpgradeSelectPackage
|
|
}
|
|
|
|
type UpgradeSelectPackage struct {
|
|
ID int
|
|
Name string
|
|
Base string
|
|
Repository string
|
|
LocalVersion string
|
|
RemoteVersion string
|
|
Reason string
|
|
LastModified int64
|
|
Maintainer string
|
|
}
|
|
|
|
type UpgradeSelectResult struct {
|
|
Exclude []string
|
|
SkipMenu bool
|
|
}
|
|
|
|
type PostInstallEvent struct {
|
|
Packages []PostInstallPackage
|
|
}
|
|
|
|
type PostInstallPackage struct {
|
|
Name string
|
|
Version string
|
|
LocalVersion string
|
|
Source string
|
|
Reason string
|
|
}
|
|
|
|
type SearchFilterEvent struct {
|
|
Results []SearchResultPackage
|
|
}
|
|
|
|
type SearchResultPackage struct {
|
|
Source string
|
|
Name string
|
|
Description string
|
|
Base string
|
|
Votes int
|
|
Popularity float64
|
|
FirstSubmitted int
|
|
LastModified int
|
|
}
|
|
|
|
type SearchResultRef struct {
|
|
Source string
|
|
Name string
|
|
}
|
|
|
|
type RenderAUREvent struct {
|
|
Name string
|
|
Version string
|
|
Description string
|
|
Base string
|
|
Votes int
|
|
Popularity float64
|
|
Maintainer string
|
|
OutOfDate int
|
|
FirstSubmitted int
|
|
LastModified int
|
|
LocalVersion string
|
|
}
|
|
|
|
type RenderSyncEvent struct {
|
|
Repository string
|
|
Name string
|
|
Description string
|
|
Version string
|
|
Groups []string
|
|
LocalVersion string
|
|
}
|
|
|
|
func (e *Engine) createAutocmd(state *glua.LState) int {
|
|
event := state.CheckString(1)
|
|
if event != EventAURPreInstall && event != EventAURPostDownload &&
|
|
event != EventUpgradeSelect && event != EventPostInstall && event != EventSearchFilter &&
|
|
event != EventRenderAUR && event != EventRenderSync {
|
|
state.ArgError(1, fmt.Sprintf("unsupported event %q", event))
|
|
return 0
|
|
}
|
|
|
|
opts := state.CheckTable(2)
|
|
callback := state.GetField(opts, "callback")
|
|
fn, ok := callback.(*glua.LFunction)
|
|
if !ok {
|
|
state.ArgError(2, "callback must be a function")
|
|
return 0
|
|
}
|
|
|
|
desc := ""
|
|
if val := state.GetField(opts, "desc"); val != glua.LNil {
|
|
str, ok := val.(glua.LString)
|
|
if !ok {
|
|
state.ArgError(2, "desc must be a string")
|
|
return 0
|
|
}
|
|
desc = string(str)
|
|
}
|
|
|
|
e.autocmds[event] = append(e.autocmds[event], Autocmd{
|
|
Event: event,
|
|
Desc: desc,
|
|
callback: fn,
|
|
})
|
|
|
|
return 0
|
|
}
|
|
|
|
func (e *Engine) HasAutocmd(event string) bool {
|
|
return e != nil && len(e.autocmds[event]) > 0
|
|
}
|
|
|
|
func (e *Engine) RunAURPreInstall(event *AURPreInstallEvent) error {
|
|
return e.runAUREvent(EventAURPreInstall, event)
|
|
}
|
|
|
|
func (e *Engine) RunAURPostDownload(event *AURPreInstallEvent) error {
|
|
return e.runAUREvent(EventAURPostDownload, event)
|
|
}
|
|
|
|
func (e *Engine) runAUREvent(eventName string, event *AURPreInstallEvent) error {
|
|
if !e.HasAutocmd(eventName) {
|
|
return nil
|
|
}
|
|
|
|
for _, autocmd := range e.autocmds[eventName] {
|
|
if err := e.L.CallByParam(glua.P{
|
|
Fn: autocmd.callback,
|
|
NRet: 0,
|
|
Protect: true,
|
|
}, e.aurEventTable(eventName, event)); err != nil {
|
|
return fmt.Errorf("%s %s: %w", eventName, event.Base, wrapLuaErr(err))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (e *Engine) RunUpgradeSelect(event *UpgradeSelectEvent) (UpgradeSelectResult, error) {
|
|
var result UpgradeSelectResult
|
|
if !e.HasAutocmd(EventUpgradeSelect) {
|
|
return result, nil
|
|
}
|
|
|
|
validExcludes := mapset.NewThreadUnsafeSetWithSize[string](len(event.Upgrades))
|
|
for i := range event.Upgrades {
|
|
validExcludes.Add(event.Upgrades[i].Name)
|
|
}
|
|
|
|
seenExcludes := mapset.NewThreadUnsafeSet[string]()
|
|
for _, autocmd := range e.autocmds[EventUpgradeSelect] {
|
|
if err := e.L.CallByParam(glua.P{
|
|
Fn: autocmd.callback,
|
|
NRet: 1,
|
|
Protect: true,
|
|
}, e.upgradeSelectTable(event)); err != nil {
|
|
return result, fmt.Errorf("%s: %w", EventUpgradeSelect, wrapLuaErr(err))
|
|
}
|
|
|
|
value := e.L.Get(-1)
|
|
e.L.Pop(1)
|
|
|
|
hookResult, err := e.parseUpgradeSelectResult(value, validExcludes)
|
|
if err != nil {
|
|
return result, fmt.Errorf("%s: %w", EventUpgradeSelect, err)
|
|
}
|
|
|
|
for _, name := range hookResult.Exclude {
|
|
if !seenExcludes.Add(name) {
|
|
continue
|
|
}
|
|
result.Exclude = append(result.Exclude, name)
|
|
}
|
|
|
|
if hookResult.SkipMenu {
|
|
result.SkipMenu = true
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (e *Engine) newEventTable(eventName string) (eventTable, data *glua.LTable) {
|
|
eventTable = e.L.NewTable()
|
|
data = e.L.NewTable()
|
|
eventTable.RawSetString("event", glua.LString(eventName))
|
|
eventTable.RawSetString("data", data)
|
|
|
|
return eventTable, data
|
|
}
|
|
|
|
func (e *Engine) aurEventTable(eventName string, event *AURPreInstallEvent) *glua.LTable {
|
|
eventTable, data := e.newEventTable(eventName)
|
|
eventTable.RawSetString("match", glua.LString(event.Base))
|
|
|
|
data.RawSetString("base", glua.LString(event.Base))
|
|
data.RawSetString("dir", glua.LString(event.Dir))
|
|
data.RawSetString("pkgbuild_path", glua.LString(event.PKGBUILDPath))
|
|
data.RawSetString("srcinfo_path", glua.LString(event.SRCINFOPath))
|
|
data.RawSetString("pkgbuild", glua.LString(event.PKGBUILD))
|
|
data.RawSetString("version", glua.LString(event.Version))
|
|
data.RawSetString("last_modified", glua.LNumber(event.LastModified))
|
|
data.RawSetString("installed", glua.LBool(event.Installed))
|
|
data.RawSetString("packages", e.packagesTable(event.Packages))
|
|
data.RawSetString("srcinfo", e.srcinfoTable(&event.SRCINFO))
|
|
|
|
return eventTable
|
|
}
|
|
|
|
func (e *Engine) upgradeSelectTable(event *UpgradeSelectEvent) *glua.LTable {
|
|
eventTable, data := e.newEventTable(EventUpgradeSelect)
|
|
data.RawSetString("upgrades", e.upgradeSelectPackagesTable(event.Upgrades))
|
|
data.RawSetString("pulled_dependencies", e.upgradeSelectPackagesTable(event.PulledDependencies))
|
|
|
|
return eventTable
|
|
}
|
|
|
|
func (e *Engine) packagesTable(packages []AURPreInstallPackage) *glua.LTable {
|
|
state := e.L
|
|
tbl := state.NewTable()
|
|
|
|
for _, pkg := range packages {
|
|
pkgTbl := state.NewTable()
|
|
pkgTbl.RawSetString("name", glua.LString(pkg.Name))
|
|
pkgTbl.RawSetString("version", glua.LString(pkg.Version))
|
|
pkgTbl.RawSetString("local_version", glua.LString(pkg.LocalVersion))
|
|
pkgTbl.RawSetString("reason", glua.LString(pkg.Reason))
|
|
pkgTbl.RawSetString("upgrade", glua.LBool(pkg.Upgrade))
|
|
pkgTbl.RawSetString("devel", glua.LBool(pkg.Devel))
|
|
tbl.Append(pkgTbl)
|
|
}
|
|
|
|
return tbl
|
|
}
|
|
|
|
func (e *Engine) upgradeSelectPackagesTable(packages []UpgradeSelectPackage) *glua.LTable {
|
|
state := e.L
|
|
tbl := state.NewTable()
|
|
|
|
for i := range packages {
|
|
pkg := &packages[i]
|
|
pkgTbl := state.NewTable()
|
|
pkgTbl.RawSetString("id", glua.LNumber(pkg.ID))
|
|
pkgTbl.RawSetString("name", glua.LString(pkg.Name))
|
|
pkgTbl.RawSetString("base", glua.LString(pkg.Base))
|
|
pkgTbl.RawSetString("repository", glua.LString(pkg.Repository))
|
|
pkgTbl.RawSetString("local_version", glua.LString(pkg.LocalVersion))
|
|
pkgTbl.RawSetString("remote_version", glua.LString(pkg.RemoteVersion))
|
|
pkgTbl.RawSetString("reason", glua.LString(pkg.Reason))
|
|
pkgTbl.RawSetString("last_modified", glua.LNumber(pkg.LastModified))
|
|
pkgTbl.RawSetString("maintainer", glua.LString(pkg.Maintainer))
|
|
tbl.Append(pkgTbl)
|
|
}
|
|
|
|
return tbl
|
|
}
|
|
|
|
func (e *Engine) srcinfoTable(srcinfo *AURPreInstallSRCINFO) *glua.LTable {
|
|
state := e.L
|
|
tbl := state.NewTable()
|
|
|
|
tbl.RawSetString("pkgbase", glua.LString(srcinfo.Pkgbase))
|
|
tbl.RawSetString("pkgver", glua.LString(srcinfo.Pkgver))
|
|
tbl.RawSetString("pkgrel", glua.LString(srcinfo.Pkgrel))
|
|
tbl.RawSetString("epoch", glua.LString(srcinfo.Epoch))
|
|
tbl.RawSetString("version", glua.LString(srcinfo.Version))
|
|
tbl.RawSetString("pkgdesc", glua.LString(srcinfo.Pkgdesc))
|
|
tbl.RawSetString("url", glua.LString(srcinfo.URL))
|
|
tbl.RawSetString("arch", e.stringArray(srcinfo.Arch))
|
|
tbl.RawSetString("license", e.stringArray(srcinfo.License))
|
|
tbl.RawSetString("depends", e.stringArray(srcinfo.Depends))
|
|
tbl.RawSetString("makedepends", e.stringArray(srcinfo.MakeDepends))
|
|
tbl.RawSetString("checkdepends", e.stringArray(srcinfo.CheckDepends))
|
|
tbl.RawSetString("optdepends", e.stringArray(srcinfo.OptDepends))
|
|
tbl.RawSetString("provides", e.stringArray(srcinfo.Provides))
|
|
tbl.RawSetString("conflicts", e.stringArray(srcinfo.Conflicts))
|
|
tbl.RawSetString("replaces", e.stringArray(srcinfo.Replaces))
|
|
|
|
return tbl
|
|
}
|
|
|
|
func (e *Engine) parseUpgradeSelectResult(value glua.LValue, validExcludes mapset.Set[string]) (UpgradeSelectResult, error) {
|
|
var result UpgradeSelectResult
|
|
if value == glua.LNil {
|
|
return result, nil
|
|
}
|
|
|
|
tbl, ok := value.(*glua.LTable)
|
|
if !ok {
|
|
return result, fmt.Errorf("callback must return nil or table, got %s", value.Type())
|
|
}
|
|
|
|
if excludeValue := tbl.RawGetString("exclude"); excludeValue != glua.LNil {
|
|
excludeTbl, ok := excludeValue.(*glua.LTable)
|
|
if !ok {
|
|
return result, fmt.Errorf("exclude must be a table")
|
|
}
|
|
|
|
var parseErr error
|
|
excludeTbl.ForEach(func(_ glua.LValue, val glua.LValue) {
|
|
if parseErr != nil {
|
|
return
|
|
}
|
|
|
|
lname, ok := val.(glua.LString)
|
|
if !ok {
|
|
parseErr = fmt.Errorf("exclude entries must be strings")
|
|
return
|
|
}
|
|
|
|
name := string(lname)
|
|
if !validExcludes.Contains(name) {
|
|
parseErr = fmt.Errorf("unknown upgrade exclusion %q", name)
|
|
return
|
|
}
|
|
|
|
result.Exclude = append(result.Exclude, name)
|
|
})
|
|
if parseErr != nil {
|
|
return result, parseErr
|
|
}
|
|
}
|
|
|
|
if skipMenuValue := tbl.RawGetString("skip_menu"); skipMenuValue != glua.LNil {
|
|
skipMenu, ok := skipMenuValue.(glua.LBool)
|
|
if !ok {
|
|
return result, fmt.Errorf("skip_menu must be a boolean")
|
|
}
|
|
|
|
result.SkipMenu = bool(skipMenu)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (e *Engine) stringArray(values []string) *glua.LTable {
|
|
tbl := e.L.NewTable()
|
|
for _, value := range values {
|
|
tbl.Append(glua.LString(value))
|
|
}
|
|
|
|
return tbl
|
|
}
|
|
|
|
func (e *Engine) RunPostInstall(event *PostInstallEvent) error {
|
|
if !e.HasAutocmd(EventPostInstall) {
|
|
return nil
|
|
}
|
|
|
|
for _, autocmd := range e.autocmds[EventPostInstall] {
|
|
if err := e.L.CallByParam(glua.P{
|
|
Fn: autocmd.callback,
|
|
NRet: 0,
|
|
Protect: true,
|
|
}, e.postInstallTable(event)); err != nil {
|
|
return fmt.Errorf("%s: %w", EventPostInstall, wrapLuaErr(err))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (e *Engine) RunSearchFilter(event *SearchFilterEvent) ([]SearchResultRef, error) {
|
|
if !e.HasAutocmd(EventSearchFilter) {
|
|
return nil, nil
|
|
}
|
|
|
|
active := event.Results
|
|
|
|
for _, autocmd := range e.autocmds[EventSearchFilter] {
|
|
// Rebuild the validation map from the current active set so that a
|
|
// later hook cannot reintroduce packages that an earlier hook dropped.
|
|
activeByRef := make(map[SearchResultRef]int, len(active))
|
|
for i, pkg := range active {
|
|
activeByRef[SearchResultRef{Source: pkg.Source, Name: pkg.Name}] = i
|
|
}
|
|
|
|
if err := e.L.CallByParam(glua.P{
|
|
Fn: autocmd.callback,
|
|
NRet: 1,
|
|
Protect: true,
|
|
}, e.searchFilterTable(active)); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", EventSearchFilter, wrapLuaErr(err))
|
|
}
|
|
|
|
value := e.L.Get(-1)
|
|
e.L.Pop(1)
|
|
|
|
refs, returned, err := parseSearchFilterResult(value, activeByRef)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s: %w", EventSearchFilter, err)
|
|
}
|
|
|
|
if !returned {
|
|
continue
|
|
}
|
|
|
|
next := make([]SearchResultPackage, 0, len(refs))
|
|
for _, ref := range refs {
|
|
next = append(next, active[activeByRef[ref]])
|
|
}
|
|
|
|
active = next
|
|
}
|
|
|
|
result := make([]SearchResultRef, len(active))
|
|
for i, pkg := range active {
|
|
result[i] = SearchResultRef{Source: pkg.Source, Name: pkg.Name}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (e *Engine) postInstallTable(event *PostInstallEvent) *glua.LTable {
|
|
eventTable, data := e.newEventTable(EventPostInstall)
|
|
data.RawSetString("packages", e.postInstallPackagesTable(event.Packages))
|
|
|
|
return eventTable
|
|
}
|
|
|
|
func (e *Engine) postInstallPackagesTable(packages []PostInstallPackage) *glua.LTable {
|
|
tbl := e.L.NewTable()
|
|
|
|
for i := range packages {
|
|
pkg := &packages[i]
|
|
pkgTbl := e.L.NewTable()
|
|
pkgTbl.RawSetString("name", glua.LString(pkg.Name))
|
|
pkgTbl.RawSetString("version", glua.LString(pkg.Version))
|
|
pkgTbl.RawSetString("local_version", glua.LString(pkg.LocalVersion))
|
|
pkgTbl.RawSetString("source", glua.LString(pkg.Source))
|
|
pkgTbl.RawSetString("reason", glua.LString(pkg.Reason))
|
|
tbl.Append(pkgTbl)
|
|
}
|
|
|
|
return tbl
|
|
}
|
|
|
|
func (e *Engine) searchFilterTable(packages []SearchResultPackage) *glua.LTable {
|
|
eventTable, data := e.newEventTable(EventSearchFilter)
|
|
data.RawSetString("results", e.searchResultPackagesTable(packages))
|
|
|
|
return eventTable
|
|
}
|
|
|
|
func (e *Engine) searchResultPackagesTable(packages []SearchResultPackage) *glua.LTable {
|
|
tbl := e.L.NewTable()
|
|
|
|
for i := range packages {
|
|
pkg := &packages[i]
|
|
pkgTbl := e.L.NewTable()
|
|
pkgTbl.RawSetString("source", glua.LString(pkg.Source))
|
|
pkgTbl.RawSetString("name", glua.LString(pkg.Name))
|
|
pkgTbl.RawSetString("description", glua.LString(pkg.Description))
|
|
pkgTbl.RawSetString("base", glua.LString(pkg.Base))
|
|
pkgTbl.RawSetString("votes", glua.LNumber(pkg.Votes))
|
|
pkgTbl.RawSetString("popularity", glua.LNumber(pkg.Popularity))
|
|
pkgTbl.RawSetString("first_submitted", glua.LNumber(pkg.FirstSubmitted))
|
|
pkgTbl.RawSetString("last_modified", glua.LNumber(pkg.LastModified))
|
|
tbl.Append(pkgTbl)
|
|
}
|
|
|
|
return tbl
|
|
}
|
|
|
|
func (e *Engine) renderAURTable(event *RenderAUREvent) *glua.LTable {
|
|
eventTable, data := e.newEventTable(EventRenderAUR)
|
|
data.RawSetString("name", glua.LString(event.Name))
|
|
data.RawSetString("version", glua.LString(event.Version))
|
|
data.RawSetString("description", glua.LString(event.Description))
|
|
data.RawSetString("base", glua.LString(event.Base))
|
|
data.RawSetString("votes", glua.LNumber(event.Votes))
|
|
data.RawSetString("popularity", glua.LNumber(event.Popularity))
|
|
data.RawSetString("maintainer", glua.LString(event.Maintainer))
|
|
data.RawSetString("out_of_date", glua.LNumber(event.OutOfDate))
|
|
data.RawSetString("first_submitted", glua.LNumber(event.FirstSubmitted))
|
|
data.RawSetString("last_modified", glua.LNumber(event.LastModified))
|
|
data.RawSetString("local_version", glua.LString(event.LocalVersion))
|
|
return eventTable
|
|
}
|
|
|
|
func (e *Engine) renderSyncTable(event *RenderSyncEvent) *glua.LTable {
|
|
eventTable, data := e.newEventTable(EventRenderSync)
|
|
data.RawSetString("repository", glua.LString(event.Repository))
|
|
data.RawSetString("name", glua.LString(event.Name))
|
|
data.RawSetString("description", glua.LString(event.Description))
|
|
data.RawSetString("version", glua.LString(event.Version))
|
|
data.RawSetString("groups", e.stringArray(event.Groups))
|
|
data.RawSetString("local_version", glua.LString(event.LocalVersion))
|
|
return eventTable
|
|
}
|
|
|
|
func (e *Engine) RunRenderAUR(event *RenderAUREvent) (rendered string, ok bool, err error) {
|
|
if !e.HasAutocmd(EventRenderAUR) {
|
|
return "", false, nil
|
|
}
|
|
|
|
return e.runRender(EventRenderAUR, func() *glua.LTable {
|
|
return e.renderAURTable(event)
|
|
})
|
|
}
|
|
|
|
func (e *Engine) RunRenderSync(event *RenderSyncEvent) (rendered string, ok bool, err error) {
|
|
if !e.HasAutocmd(EventRenderSync) {
|
|
return "", false, nil
|
|
}
|
|
|
|
return e.runRender(EventRenderSync, func() *glua.LTable {
|
|
return e.renderSyncTable(event)
|
|
})
|
|
}
|
|
|
|
func (e *Engine) runRender(eventName string, newEventTable func() *glua.LTable) (rendered string, ok bool, err error) {
|
|
for _, autocmd := range e.autocmds[eventName] {
|
|
if err := e.L.CallByParam(glua.P{
|
|
Fn: autocmd.callback,
|
|
NRet: 1,
|
|
Protect: true,
|
|
}, newEventTable()); err != nil {
|
|
return "", false, fmt.Errorf("%s: %w", eventName, wrapLuaErr(err))
|
|
}
|
|
|
|
value := e.L.Get(-1)
|
|
e.L.Pop(1)
|
|
|
|
if value == glua.LNil {
|
|
continue
|
|
}
|
|
|
|
str, isStr := value.(glua.LString)
|
|
if !isStr {
|
|
return "", false, fmt.Errorf("%s: callback must return a string or nil, got %s", eventName, value.Type())
|
|
}
|
|
|
|
rendered = string(str)
|
|
ok = true
|
|
}
|
|
|
|
return rendered, ok, nil
|
|
}
|
|
|
|
func parseSearchFilterResult(value glua.LValue, valid map[SearchResultRef]int) ([]SearchResultRef, bool, error) {
|
|
if value == glua.LNil {
|
|
return nil, false, nil
|
|
}
|
|
|
|
tbl, ok := value.(*glua.LTable)
|
|
if !ok {
|
|
return nil, false, fmt.Errorf("callback must return nil or a table, got %s", value.Type())
|
|
}
|
|
|
|
var (
|
|
refs []SearchResultRef
|
|
parseErr error
|
|
)
|
|
|
|
seen := mapset.NewThreadUnsafeSet[SearchResultRef]()
|
|
|
|
tbl.ForEach(func(_ glua.LValue, val glua.LValue) {
|
|
if parseErr != nil {
|
|
return
|
|
}
|
|
|
|
entry, ok := val.(*glua.LTable)
|
|
if !ok {
|
|
parseErr = fmt.Errorf("each result must be a table")
|
|
return
|
|
}
|
|
|
|
source, ok := entry.RawGetString("source").(glua.LString)
|
|
if !ok {
|
|
parseErr = fmt.Errorf("result source must be a string")
|
|
return
|
|
}
|
|
|
|
name, ok := entry.RawGetString("name").(glua.LString)
|
|
if !ok {
|
|
parseErr = fmt.Errorf("result name must be a string")
|
|
return
|
|
}
|
|
|
|
ref := SearchResultRef{Source: string(source), Name: string(name)}
|
|
if _, exists := valid[ref]; !exists {
|
|
parseErr = fmt.Errorf("unknown search result %s/%s", ref.Source, ref.Name)
|
|
return
|
|
}
|
|
|
|
if !seen.Add(ref) {
|
|
return
|
|
}
|
|
|
|
refs = append(refs, ref)
|
|
})
|
|
|
|
if parseErr != nil {
|
|
return nil, false, parseErr
|
|
}
|
|
|
|
return refs, true, nil
|
|
}
|