mirror of
https://github.com/Jguer/yay.git
synced 2026-07-24 05:27:44 -04:00
646 lines
18 KiB
Go
646 lines
18 KiB
Go
// Package lua provides an experimental Lua-based configuration surface for
|
|
// yay, mirroring neovim's `init.lua` pattern.
|
|
//
|
|
// The user writes a script at $XDG_CONFIG_HOME/yay/init.lua. The script is run
|
|
// inside a fresh gopher-lua state where a global `yay` table is pre-installed:
|
|
//
|
|
// yay.opt.<key> -- setter proxy; values are collected and applied
|
|
// to a *settings.Configuration via reflection over
|
|
// its `ini:"..."` struct tags. Both snake_case and
|
|
// PascalCase keys are accepted (case-insensitive).
|
|
// yay.hook.on_prompt -- function(name, default) -> string. When set, it
|
|
// is invoked by menu code in lieu of the default
|
|
// answer. Returning nil/false falls back to the
|
|
// default.
|
|
//
|
|
// Apply takes a generic struct pointer and uses reflection so normal config
|
|
// fields do not need bespoke Lua bindings.
|
|
package lua
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/Jguer/yay/v12/pkg/settings"
|
|
lua "github.com/yuin/gopher-lua"
|
|
)
|
|
|
|
const (
|
|
globalName = "yay"
|
|
optTableName = "opt"
|
|
apiTableName = "api"
|
|
hookTableName = "hook"
|
|
providerTableName = "provider"
|
|
hookOnPrompt = "on_prompt"
|
|
hookShouldIncludeAURUpdate = "should_include_aur_update"
|
|
providerListFunctionName = "list"
|
|
providerSearchFunctionName = "search"
|
|
providerInstallFunctionName = "install"
|
|
providerRunFunctionName = "upgrade"
|
|
)
|
|
|
|
// Engine wraps a gopher-lua state and serializes access to it.
|
|
type Engine struct {
|
|
mu sync.Mutex
|
|
L *lua.LState
|
|
callContext context.Context
|
|
}
|
|
|
|
// New creates a new Lua engine with the `yay` global installed.
|
|
func New() *Engine {
|
|
L := lua.NewState()
|
|
|
|
yayTbl := L.NewTable()
|
|
L.SetGlobal(globalName, yayTbl)
|
|
|
|
// yay.opt: plain table; values are read after the script runs.
|
|
L.SetField(yayTbl, optTableName, L.NewTable())
|
|
// yay.api: helper functions exposed to init.lua and runtime hooks.
|
|
L.SetField(yayTbl, apiTableName, L.NewTable())
|
|
// yay.hook: plain table; functions stored here are invoked from Go.
|
|
L.SetField(yayTbl, hookTableName, L.NewTable())
|
|
// yay.provider: registry of custom upgrade providers managed by Lua.
|
|
L.SetField(yayTbl, providerTableName, L.NewTable())
|
|
|
|
return &Engine{L: L}
|
|
}
|
|
|
|
func (e *Engine) contextOrBackground() context.Context {
|
|
if e == nil || e.callContext == nil {
|
|
return context.Background()
|
|
}
|
|
|
|
return e.callContext
|
|
}
|
|
|
|
// SetAPI registers a helper function under yay.api.<name>.
|
|
func (e *Engine) SetAPI(name string, fn lua.LGFunction) error {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
yayTbl, ok := e.L.GetGlobal(globalName).(*lua.LTable)
|
|
if !ok {
|
|
return fmt.Errorf("lua: missing %s table", globalName)
|
|
}
|
|
apiTbl, ok := e.L.GetField(yayTbl, apiTableName).(*lua.LTable)
|
|
if !ok {
|
|
return fmt.Errorf("lua: missing %s.%s table", globalName, apiTableName)
|
|
}
|
|
|
|
e.L.SetField(apiTbl, name, e.L.NewFunction(fn))
|
|
return nil
|
|
}
|
|
|
|
// Close releases the Lua state.
|
|
func (e *Engine) Close() {
|
|
if e == nil || e.L == nil {
|
|
return
|
|
}
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
e.L.Close()
|
|
e.L = nil
|
|
}
|
|
|
|
// Run executes the Lua file at path.
|
|
func (e *Engine) Run(path string) error {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
if err := e.L.DoFile(path); err != nil {
|
|
return fmt.Errorf("lua: error running %s: %w", path, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RunString executes an inline Lua snippet. Primarily for tests.
|
|
func (e *Engine) RunString(src string) error {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
if err := e.L.DoString(src); err != nil {
|
|
return fmt.Errorf("lua: error running snippet: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Apply walks the `yay.opt` table and writes recognized keys into cfg via
|
|
// reflection. cfg must be a pointer to a struct whose fields are tagged with
|
|
// `ini:"<name>"`. Unknown keys are returned via the unknown slice (caller can
|
|
// log them); type-mismatches are reported in errs.
|
|
func (e *Engine) Apply(cfg any) (unknown []string, errs []error) {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
v := reflect.ValueOf(cfg)
|
|
if v.Kind() != reflect.Pointer || v.Elem().Kind() != reflect.Struct {
|
|
return nil, []error{fmt.Errorf("lua: Apply: expected pointer to struct, got %T", cfg)}
|
|
}
|
|
sv := v.Elem()
|
|
st := sv.Type()
|
|
|
|
// Build a case-insensitive index of ini tag -> field index.
|
|
index := make(map[string]int, st.NumField())
|
|
for i := 0; i < st.NumField(); i++ {
|
|
tag := st.Field(i).Tag.Get("ini")
|
|
if tag == "" || tag == "-" {
|
|
continue
|
|
}
|
|
index[strings.ToLower(tag)] = i
|
|
}
|
|
|
|
yayTbl, ok := e.L.GetGlobal(globalName).(*lua.LTable)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
optTbl, ok := e.L.GetField(yayTbl, optTableName).(*lua.LTable)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
optTbl.ForEach(func(k, val lua.LValue) {
|
|
key, ok := k.(lua.LString)
|
|
if !ok {
|
|
return
|
|
}
|
|
canonical := strings.ToLower(strings.ReplaceAll(string(key), "_", ""))
|
|
fieldIdx, found := index[canonical]
|
|
if !found {
|
|
unknown = append(unknown, string(key))
|
|
return
|
|
}
|
|
if err := assign(sv.Field(fieldIdx), val); err != nil {
|
|
errs = append(errs, fmt.Errorf("yay.opt.%s: %w", string(key), err))
|
|
}
|
|
})
|
|
|
|
return unknown, errs
|
|
}
|
|
|
|
// assign writes a Lua value into a reflect.Value, performing basic coercion.
|
|
func assign(field reflect.Value, val lua.LValue) error {
|
|
if !field.CanSet() {
|
|
return fmt.Errorf("field is not settable")
|
|
}
|
|
switch field.Kind() {
|
|
case reflect.String:
|
|
s, ok := val.(lua.LString)
|
|
if !ok {
|
|
return fmt.Errorf("expected string, got %s", val.Type())
|
|
}
|
|
field.SetString(string(s))
|
|
case reflect.Bool:
|
|
b, ok := val.(lua.LBool)
|
|
if !ok {
|
|
return fmt.Errorf("expected boolean, got %s", val.Type())
|
|
}
|
|
field.SetBool(bool(b))
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
n, ok := val.(lua.LNumber)
|
|
if !ok {
|
|
return fmt.Errorf("expected number, got %s", val.Type())
|
|
}
|
|
field.SetInt(int64(n))
|
|
default:
|
|
return fmt.Errorf("unsupported field kind %s", field.Kind())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CallOnPrompt invokes yay.hook.on_prompt(name, defaultAns) if it is a
|
|
// function. The boolean return is false when the hook is not set, when it
|
|
// returns nil/false, or when it returns a non-string value. Errors from the
|
|
// Lua call are returned untouched.
|
|
func (e *Engine) CallOnPrompt(name, defaultAns string) (string, bool, error) {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
yayTbl, ok := e.L.GetGlobal(globalName).(*lua.LTable)
|
|
if !ok {
|
|
return "", false, nil
|
|
}
|
|
hookTbl, ok := e.L.GetField(yayTbl, hookTableName).(*lua.LTable)
|
|
if !ok {
|
|
return "", false, nil
|
|
}
|
|
fn, ok := e.L.GetField(hookTbl, hookOnPrompt).(*lua.LFunction)
|
|
if !ok {
|
|
return "", false, nil
|
|
}
|
|
|
|
if err := e.L.CallByParam(lua.P{
|
|
Fn: fn,
|
|
NRet: 1,
|
|
Protect: true,
|
|
}, lua.LString(name), lua.LString(defaultAns)); err != nil {
|
|
return "", false, fmt.Errorf("lua: on_prompt: %w", err)
|
|
}
|
|
ret := e.L.Get(-1)
|
|
e.L.Pop(1)
|
|
|
|
switch v := ret.(type) {
|
|
case lua.LString:
|
|
return string(v), true, nil
|
|
case *lua.LNilType:
|
|
return "", false, nil
|
|
case lua.LBool:
|
|
if !bool(v) {
|
|
return "", false, nil
|
|
}
|
|
}
|
|
return "", false, nil
|
|
}
|
|
|
|
// CallShouldIncludeAURUpdate invokes yay.hook.should_include_aur_update(candidate)
|
|
// if it is a function. The second return value is false when the hook is not
|
|
// set or returns nil.
|
|
func (e *Engine) CallShouldIncludeAURUpdate(candidate settings.AURUpdateContext) (bool, bool, error) {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
yayTbl, ok := e.L.GetGlobal(globalName).(*lua.LTable)
|
|
if !ok {
|
|
return false, false, nil
|
|
}
|
|
hookTbl, ok := e.L.GetField(yayTbl, hookTableName).(*lua.LTable)
|
|
if !ok {
|
|
return false, false, nil
|
|
}
|
|
fn, ok := e.L.GetField(hookTbl, hookShouldIncludeAURUpdate).(*lua.LFunction)
|
|
if !ok {
|
|
return false, false, nil
|
|
}
|
|
|
|
arg := e.L.NewTable()
|
|
e.L.SetField(arg, "name", lua.LString(candidate.Name))
|
|
e.L.SetField(arg, "base", lua.LString(candidate.Base))
|
|
e.L.SetField(arg, "repository", lua.LString(candidate.Repository))
|
|
e.L.SetField(arg, "local_version", lua.LString(candidate.LocalVersion))
|
|
e.L.SetField(arg, "remote_version", lua.LString(candidate.RemoteVersion))
|
|
e.L.SetField(arg, "local_build_date", lua.LNumber(candidate.LocalBuildDate))
|
|
e.L.SetField(arg, "remote_last_modified", lua.LNumber(candidate.RemoteLastModified))
|
|
e.L.SetField(arg, "default_include", lua.LBool(candidate.DefaultInclude))
|
|
|
|
if err := e.L.CallByParam(lua.P{
|
|
Fn: fn,
|
|
NRet: 1,
|
|
Protect: true,
|
|
}, arg); err != nil {
|
|
return false, false, fmt.Errorf("lua: should_include_aur_update: %w", err)
|
|
}
|
|
ret := e.L.Get(-1)
|
|
e.L.Pop(1)
|
|
|
|
switch v := ret.(type) {
|
|
case lua.LBool:
|
|
return bool(v), true, nil
|
|
case *lua.LNilType:
|
|
return false, false, nil
|
|
default:
|
|
return false, false, fmt.Errorf("lua: should_include_aur_update: expected boolean or nil, got %s", ret.Type())
|
|
}
|
|
}
|
|
|
|
// CallListExternalUpgrades invokes all registered yay.provider.<name>.list
|
|
// functions and merges their returned upgrades into a single slice.
|
|
func (e *Engine) CallListExternalUpgrades(ctx context.Context) ([]settings.ExternalUpgrade, error) {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
previousCtx := e.callContext
|
|
e.callContext = ctx
|
|
defer func() { e.callContext = previousCtx }()
|
|
|
|
providers, err := e.providersTable()
|
|
if err != nil || providers == nil {
|
|
return nil, err
|
|
}
|
|
providerNames, providerTables, err := e.collectProviders(providers)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
upgrades := make([]settings.ExternalUpgrade, 0)
|
|
for _, providerName := range providerNames {
|
|
provider := providerTables[providerName]
|
|
fn, ok := e.L.GetField(provider, providerListFunctionName).(*lua.LFunction)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if err := e.L.CallByParam(lua.P{Fn: fn, NRet: 1, Protect: true}); err != nil {
|
|
return nil, fmt.Errorf("lua: provider %s list: %w", providerName, err)
|
|
}
|
|
|
|
ret := e.L.Get(-1)
|
|
e.L.Pop(1)
|
|
|
|
if ret == lua.LNil {
|
|
continue
|
|
}
|
|
|
|
items, ok := ret.(*lua.LTable)
|
|
if !ok {
|
|
return nil, fmt.Errorf("lua: provider %s list: expected table or nil, got %s", providerName, ret.Type())
|
|
}
|
|
|
|
providerUpgrades, err := e.externalUpgradesFromLua(providerName, items)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
upgrades = append(upgrades, providerUpgrades...)
|
|
}
|
|
|
|
return upgrades, nil
|
|
}
|
|
|
|
// CallRunExternalUpgrades invokes yay.provider.<name>.upgrade for the selected
|
|
// upgrades. The boolean return is false when the provider or hook is absent.
|
|
func (e *Engine) CallRunExternalUpgrades(ctx context.Context, repository string, upgrades []settings.ExternalUpgrade) (bool, error) {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
previousCtx := e.callContext
|
|
e.callContext = ctx
|
|
defer func() { e.callContext = previousCtx }()
|
|
|
|
providers, err := e.providersTable()
|
|
if err != nil || providers == nil {
|
|
return false, err
|
|
}
|
|
|
|
provider, ok := e.L.GetField(providers, repository).(*lua.LTable)
|
|
if !ok {
|
|
return false, nil
|
|
}
|
|
|
|
fn, ok := e.L.GetField(provider, providerRunFunctionName).(*lua.LFunction)
|
|
if !ok {
|
|
return false, nil
|
|
}
|
|
|
|
if err := e.L.CallByParam(lua.P{Fn: fn, NRet: 0, Protect: true}, e.externalUpgradesToLua(upgrades)); err != nil {
|
|
return true, fmt.Errorf("lua: provider %s upgrade: %w", repository, err)
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// CallSearchExternalPackages invokes all registered yay.provider.<name>.search
|
|
// functions and merges their returned results into a single slice.
|
|
func (e *Engine) CallSearchExternalPackages(ctx context.Context, terms []string) ([]settings.ExternalSearchResult, error) {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
previousCtx := e.callContext
|
|
e.callContext = ctx
|
|
defer func() { e.callContext = previousCtx }()
|
|
|
|
providers, err := e.providersTable()
|
|
if err != nil || providers == nil {
|
|
return nil, err
|
|
}
|
|
providerNames, providerTables, err := e.collectProviders(providers)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
termsTable := e.L.NewTable()
|
|
for _, term := range terms {
|
|
termsTable.Append(lua.LString(term))
|
|
}
|
|
|
|
results := make([]settings.ExternalSearchResult, 0)
|
|
for _, providerName := range providerNames {
|
|
provider := providerTables[providerName]
|
|
fn, ok := e.L.GetField(provider, providerSearchFunctionName).(*lua.LFunction)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if err := e.L.CallByParam(lua.P{Fn: fn, NRet: 1, Protect: true}, termsTable); err != nil {
|
|
return nil, fmt.Errorf("lua: provider %s search: %w", providerName, err)
|
|
}
|
|
|
|
ret := e.L.Get(-1)
|
|
e.L.Pop(1)
|
|
if ret == lua.LNil {
|
|
continue
|
|
}
|
|
|
|
items, ok := ret.(*lua.LTable)
|
|
if !ok {
|
|
return nil, fmt.Errorf("lua: provider %s search: expected table or nil, got %s", providerName, ret.Type())
|
|
}
|
|
|
|
providerResults, err := e.externalSearchResultsFromLua(providerName, items)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
results = append(results, providerResults...)
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// CallInstallExternalPackages invokes yay.provider.<name>.install for the
|
|
// selected targets. The boolean return is false when the provider or hook is
|
|
// absent.
|
|
func (e *Engine) CallInstallExternalPackages(ctx context.Context, repository string, targets []settings.ExternalInstallTarget) (bool, error) {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
previousCtx := e.callContext
|
|
e.callContext = ctx
|
|
defer func() { e.callContext = previousCtx }()
|
|
|
|
providers, err := e.providersTable()
|
|
if err != nil || providers == nil {
|
|
return false, err
|
|
}
|
|
|
|
provider, ok := e.L.GetField(providers, repository).(*lua.LTable)
|
|
if !ok {
|
|
return false, nil
|
|
}
|
|
|
|
fn, ok := e.L.GetField(provider, providerInstallFunctionName).(*lua.LFunction)
|
|
if !ok {
|
|
return false, nil
|
|
}
|
|
|
|
if err := e.L.CallByParam(lua.P{Fn: fn, NRet: 0, Protect: true}, e.externalInstallTargetsToLua(targets)); err != nil {
|
|
return true, fmt.Errorf("lua: provider %s install: %w", repository, err)
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (e *Engine) HasProvider(name string) bool {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
providers, err := e.providersTable()
|
|
if err != nil || providers == nil {
|
|
return false
|
|
}
|
|
|
|
_, ok := e.L.GetField(providers, name).(*lua.LTable)
|
|
return ok
|
|
}
|
|
|
|
func (e *Engine) providersTable() (*lua.LTable, error) {
|
|
yayTbl, ok := e.L.GetGlobal(globalName).(*lua.LTable)
|
|
if !ok {
|
|
return nil, fmt.Errorf("lua: missing %s table", globalName)
|
|
}
|
|
|
|
providers, ok := e.L.GetField(yayTbl, providerTableName).(*lua.LTable)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
return providers, nil
|
|
}
|
|
|
|
func (e *Engine) collectProviders(providers *lua.LTable) ([]string, map[string]*lua.LTable, error) {
|
|
providerNames := make([]string, 0)
|
|
providerTables := make(map[string]*lua.LTable)
|
|
providers.ForEach(func(k, v lua.LValue) {
|
|
name, ok := k.(lua.LString)
|
|
if !ok {
|
|
return
|
|
}
|
|
provider, ok := v.(*lua.LTable)
|
|
if !ok {
|
|
return
|
|
}
|
|
providerName := string(name)
|
|
providerNames = append(providerNames, providerName)
|
|
providerTables[providerName] = provider
|
|
})
|
|
sort.Strings(providerNames)
|
|
|
|
return providerNames, providerTables, nil
|
|
}
|
|
|
|
func (e *Engine) externalUpgradesFromLua(providerName string, items *lua.LTable) ([]settings.ExternalUpgrade, error) {
|
|
upgrades := make([]settings.ExternalUpgrade, 0)
|
|
items.ForEach(func(_ lua.LValue, value lua.LValue) {
|
|
if value == lua.LNil {
|
|
return
|
|
}
|
|
item, ok := value.(*lua.LTable)
|
|
if !ok {
|
|
upgrades = append(upgrades, settings.ExternalUpgrade{Repository: providerName, Extra: fmt.Sprintf("__error__:%s", value.Type())})
|
|
return
|
|
}
|
|
|
|
upgrade := settings.ExternalUpgrade{
|
|
Name: luaTableString(item, "name"),
|
|
Base: luaTableString(item, "base"),
|
|
Repository: luaTableString(item, "repository"),
|
|
LocalVersion: luaTableString(item, "local_version"),
|
|
RemoteVersion: luaTableString(item, "remote_version"),
|
|
Extra: luaTableString(item, "extra"),
|
|
}
|
|
if upgrade.Repository == "" {
|
|
upgrade.Repository = providerName
|
|
}
|
|
upgrades = append(upgrades, upgrade)
|
|
})
|
|
|
|
for _, upgrade := range upgrades {
|
|
if strings.HasPrefix(upgrade.Extra, "__error__:") {
|
|
return nil, fmt.Errorf("lua: provider %s list: expected table items, got %s", providerName, strings.TrimPrefix(upgrade.Extra, "__error__:"))
|
|
}
|
|
if upgrade.Name == "" {
|
|
return nil, fmt.Errorf("lua: provider %s list: upgrade entry missing name", providerName)
|
|
}
|
|
}
|
|
|
|
return upgrades, nil
|
|
}
|
|
|
|
func (e *Engine) externalUpgradesToLua(upgrades []settings.ExternalUpgrade) *lua.LTable {
|
|
tbl := e.L.NewTable()
|
|
for _, upgrade := range upgrades {
|
|
item := e.L.NewTable()
|
|
e.L.SetField(item, "name", lua.LString(upgrade.Name))
|
|
e.L.SetField(item, "base", lua.LString(upgrade.Base))
|
|
e.L.SetField(item, "repository", lua.LString(upgrade.Repository))
|
|
e.L.SetField(item, "local_version", lua.LString(upgrade.LocalVersion))
|
|
e.L.SetField(item, "remote_version", lua.LString(upgrade.RemoteVersion))
|
|
e.L.SetField(item, "extra", lua.LString(upgrade.Extra))
|
|
tbl.Append(item)
|
|
}
|
|
|
|
return tbl
|
|
}
|
|
|
|
func (e *Engine) externalSearchResultsFromLua(providerName string, items *lua.LTable) ([]settings.ExternalSearchResult, error) {
|
|
results := make([]settings.ExternalSearchResult, 0)
|
|
items.ForEach(func(_ lua.LValue, value lua.LValue) {
|
|
if value == lua.LNil {
|
|
return
|
|
}
|
|
item, ok := value.(*lua.LTable)
|
|
if !ok {
|
|
results = append(results, settings.ExternalSearchResult{Repository: providerName, Extra: fmt.Sprintf("__error__:%s", value.Type())})
|
|
return
|
|
}
|
|
|
|
result := settings.ExternalSearchResult{
|
|
Name: luaTableString(item, "name"),
|
|
Base: luaTableString(item, "base"),
|
|
Repository: luaTableString(item, "repository"),
|
|
Version: luaTableString(item, "version"),
|
|
InstalledVersion: luaTableString(item, "installed_version"),
|
|
Description: luaTableString(item, "description"),
|
|
Extra: luaTableString(item, "extra"),
|
|
}
|
|
if result.Repository == "" {
|
|
result.Repository = providerName
|
|
}
|
|
results = append(results, result)
|
|
})
|
|
|
|
for _, result := range results {
|
|
if strings.HasPrefix(result.Extra, "__error__:") {
|
|
return nil, fmt.Errorf("lua: provider %s search: expected table items, got %s", providerName, strings.TrimPrefix(result.Extra, "__error__:"))
|
|
}
|
|
if result.Name == "" {
|
|
return nil, fmt.Errorf("lua: provider %s search: result entry missing name", providerName)
|
|
}
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
func (e *Engine) externalInstallTargetsToLua(targets []settings.ExternalInstallTarget) *lua.LTable {
|
|
tbl := e.L.NewTable()
|
|
for _, target := range targets {
|
|
item := e.L.NewTable()
|
|
e.L.SetField(item, "name", lua.LString(target.Name))
|
|
e.L.SetField(item, "repository", lua.LString(target.Repository))
|
|
tbl.Append(item)
|
|
}
|
|
|
|
return tbl
|
|
}
|
|
|
|
func luaTableString(tbl *lua.LTable, field string) string {
|
|
value := tbl.RawGetString(field)
|
|
if value == lua.LNil {
|
|
return ""
|
|
}
|
|
if str, ok := value.(lua.LString); ok {
|
|
return string(str)
|
|
}
|
|
|
|
return fmt.Sprint(value)
|
|
}
|