mirror of
https://github.com/Jguer/yay.git
synced 2026-07-17 10:07:05 -04:00
181 lines
3.9 KiB
Go
181 lines
3.9 KiB
Go
// Package lua loads yay's optional init.lua configuration.
|
|
package lua
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/Jguer/yay/v13/pkg/text"
|
|
|
|
lua "github.com/yuin/gopher-lua"
|
|
)
|
|
|
|
const (
|
|
globalName = "yay"
|
|
optTableName = "opt"
|
|
)
|
|
|
|
type Engine struct {
|
|
L *lua.LState
|
|
autocmds map[string][]Autocmd
|
|
logger *text.Logger
|
|
}
|
|
|
|
func New() *Engine {
|
|
return NewWithLogger(nil)
|
|
}
|
|
|
|
func NewWithLogger(logger *text.Logger) *Engine {
|
|
state := lua.NewState()
|
|
engine := &Engine{
|
|
L: state,
|
|
autocmds: make(map[string][]Autocmd),
|
|
logger: logger,
|
|
}
|
|
|
|
yayTbl := state.NewTable()
|
|
state.SetGlobal(globalName, yayTbl)
|
|
state.SetField(yayTbl, optTableName, state.NewTable())
|
|
state.SetField(yayTbl, "abort", state.NewFunction(abort))
|
|
state.SetField(yayTbl, "create_autocmd", state.NewFunction(engine.createAutocmd))
|
|
engine.registerLog(yayTbl)
|
|
|
|
return engine
|
|
}
|
|
|
|
func (e *Engine) SetLogger(logger *text.Logger) {
|
|
e.logger = logger
|
|
}
|
|
|
|
func (e *Engine) Close() {
|
|
e.L.Close()
|
|
}
|
|
|
|
// Apply writes recognized yay.opt values into cfg.
|
|
func (e *Engine) Apply(cfg any) (unknown []string, errs []error) {
|
|
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()
|
|
|
|
index := make(map[string]int, st.NumField())
|
|
|
|
for i := range st.NumField() {
|
|
field := st.Field(i)
|
|
if name := luaKeyForField(&field); name != "" {
|
|
index[name] = i
|
|
}
|
|
}
|
|
|
|
optTbl, ok := e.optTable()
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
optTbl.ForEach(func(k, val lua.LValue) {
|
|
key, ok := k.(lua.LString)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
fieldIdx, found := index[string(key)]
|
|
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
|
|
}
|
|
|
|
func (e *Engine) optTable() (*lua.LTable, bool) {
|
|
yayTbl, ok := e.L.GetGlobal(globalName).(*lua.LTable)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
|
|
optTbl, ok := e.L.GetField(yayTbl, optTableName).(*lua.LTable)
|
|
|
|
return optTbl, ok
|
|
}
|
|
|
|
// SetSearchDir makes require() resolve modules relative to dir (e.g.
|
|
// require("hooks.maintainer_change")) and drops the default "./?.lua" entry so
|
|
// require() searches only dir and the absolute system paths, never an unrelated
|
|
// CWD. When dir is the CWD, the prepended patterns already cover it.
|
|
func (e *Engine) SetSearchDir(dir string) {
|
|
pkg := e.L.GetGlobal("package")
|
|
|
|
current, ok := e.L.GetField(pkg, "path").(lua.LString)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
pattern := filepath.Join(dir, "?.lua") + ";" + filepath.Join(dir, "?", "init.lua")
|
|
e.L.SetField(pkg, "path", lua.LString(pattern+";"+absolutePatterns(string(current))))
|
|
}
|
|
|
|
// absolutePatterns keeps only the absolute entries of a package.path string,
|
|
// dropping CWD-relative ones such as "./?.lua".
|
|
func absolutePatterns(path string) string {
|
|
segments := strings.Split(path, ";")
|
|
kept := segments[:0]
|
|
|
|
for _, seg := range segments {
|
|
if filepath.IsAbs(seg) {
|
|
kept = append(kept, seg)
|
|
}
|
|
}
|
|
|
|
return strings.Join(kept, ";")
|
|
}
|
|
|
|
func luaKeyForField(field *reflect.StructField) string {
|
|
name := field.Tag.Get("lua")
|
|
if name != "" && name != "-" {
|
|
return name
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func assign(field reflect.Value, val lua.LValue) error {
|
|
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.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
|
|
}
|