mirror of
https://github.com/Jguer/yay.git
synced 2026-07-23 23:37:37 -04:00
208 lines
4.4 KiB
Go
208 lines
4.4 KiB
Go
package lua
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
|
|
"github.com/Jguer/yay/v12/pkg/settings"
|
|
"github.com/Jguer/yay/v12/pkg/text"
|
|
lua "github.com/yuin/gopher-lua"
|
|
)
|
|
|
|
func registerBuiltins(e *Engine, logger *text.Logger) error {
|
|
bindings := map[string]lua.LGFunction{
|
|
"getenv": func(L *lua.LState) int {
|
|
key := L.CheckString(1)
|
|
value, ok := os.LookupEnv(key)
|
|
if !ok {
|
|
L.Push(lua.LNil)
|
|
return 1
|
|
}
|
|
L.Push(lua.LString(value))
|
|
return 1
|
|
},
|
|
"expand": func(L *lua.LState) int {
|
|
value := os.ExpandEnv(L.CheckString(1))
|
|
if len(value) >= 2 && value[:2] == "~/" {
|
|
value = filepath.Join(os.Getenv("HOME"), value[2:])
|
|
}
|
|
L.Push(lua.LString(value))
|
|
return 1
|
|
},
|
|
"info": func(L *lua.LState) int {
|
|
if logger != nil {
|
|
logger.Infoln(L.CheckString(1))
|
|
}
|
|
return 0
|
|
},
|
|
"warn": func(L *lua.LState) int {
|
|
if logger != nil {
|
|
logger.Warnln(L.CheckString(1))
|
|
}
|
|
return 0
|
|
},
|
|
"error": func(L *lua.LState) int {
|
|
if logger != nil {
|
|
logger.Errorln(L.CheckString(1))
|
|
}
|
|
return 0
|
|
},
|
|
"capture": func(L *lua.LState) int {
|
|
args := luaCommandArgs(L)
|
|
cmd := exec.CommandContext(e.contextOrBackground(), args[0], args[1:]...)
|
|
stdout, stderr, exitCode := runCapture(cmd)
|
|
L.Push(lua.LString(stdout))
|
|
L.Push(lua.LString(stderr))
|
|
L.Push(lua.LNumber(exitCode))
|
|
return 3
|
|
},
|
|
"run": func(L *lua.LState) int {
|
|
args := luaCommandArgs(L)
|
|
cmd := exec.CommandContext(e.contextOrBackground(), args[0], args[1:]...)
|
|
cmd.Stdin = os.Stdin
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
err := cmd.Run()
|
|
L.Push(lua.LNumber(commandExitCode(err)))
|
|
return 1
|
|
},
|
|
"json_decode": func(L *lua.LState) int {
|
|
input := L.CheckString(1)
|
|
var decoded any
|
|
if err := json.Unmarshal([]byte(input), &decoded); err != nil {
|
|
L.RaiseError("json_decode: %v", err)
|
|
return 0
|
|
}
|
|
L.Push(toLuaValue(L, decoded))
|
|
return 1
|
|
},
|
|
}
|
|
|
|
for name, fn := range bindings {
|
|
if err := e.SetAPI(name, fn); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func luaCommandArgs(L *lua.LState) []string {
|
|
args := make([]string, 0, max(L.GetTop(), 1))
|
|
if tbl, ok := L.Get(1).(*lua.LTable); ok {
|
|
tbl.ForEach(func(_ lua.LValue, value lua.LValue) {
|
|
args = append(args, value.String())
|
|
})
|
|
} else {
|
|
for i := 1; i <= L.GetTop(); i++ {
|
|
args = append(args, L.CheckString(i))
|
|
}
|
|
}
|
|
|
|
if len(args) == 0 {
|
|
L.ArgError(1, "expected command")
|
|
}
|
|
|
|
return args
|
|
}
|
|
|
|
func runCapture(cmd *exec.Cmd) (stdout, stderr string, code int) {
|
|
output, err := cmd.Output()
|
|
stdout = string(output)
|
|
if err == nil {
|
|
return stdout, "", 0
|
|
}
|
|
|
|
code = commandExitCode(err)
|
|
exitErr := &exec.ExitError{}
|
|
if errors.As(err, &exitErr) {
|
|
stderr = string(exitErr.Stderr)
|
|
return stdout, stderr, code
|
|
}
|
|
|
|
return stdout, err.Error(), code
|
|
}
|
|
|
|
func commandExitCode(err error) int {
|
|
if err == nil {
|
|
return 0
|
|
}
|
|
|
|
exitErr := &exec.ExitError{}
|
|
if errors.As(err, &exitErr) {
|
|
return exitErr.ExitCode()
|
|
}
|
|
|
|
return -1
|
|
}
|
|
|
|
func toLuaValue(L *lua.LState, value any) lua.LValue {
|
|
switch v := value.(type) {
|
|
case nil:
|
|
return lua.LNil
|
|
case string:
|
|
return lua.LString(v)
|
|
case bool:
|
|
return lua.LBool(v)
|
|
case float64:
|
|
return lua.LNumber(v)
|
|
case []any:
|
|
tbl := L.NewTable()
|
|
for _, item := range v {
|
|
tbl.Append(toLuaValue(L, item))
|
|
}
|
|
return tbl
|
|
case map[string]any:
|
|
tbl := L.NewTable()
|
|
for key, item := range v {
|
|
L.SetField(tbl, key, toLuaValue(L, item))
|
|
}
|
|
return tbl
|
|
default:
|
|
return lua.LString(fmt.Sprint(v))
|
|
}
|
|
}
|
|
|
|
// LoadInto runs the user's init.lua (if present) and applies its `yay.opt`
|
|
// table onto cfg, then attaches the engine to cfg so hooks can be invoked at
|
|
// runtime. If no init.lua is present, LoadInto is a no-op and returns nil.
|
|
//
|
|
// Errors from Apply (type mismatches) and unknown keys are reported via
|
|
// logger but do not fail the load.
|
|
func LoadInto(logger *text.Logger, cfg *settings.Configuration) error {
|
|
path, err := settings.ResolveLuaConfigPath()
|
|
if err != nil {
|
|
return fmt.Errorf("lua: resolve init.lua: %w", err)
|
|
}
|
|
if path == "" {
|
|
return nil
|
|
}
|
|
|
|
e := New()
|
|
if err := registerBuiltins(e, logger); err != nil {
|
|
e.Close()
|
|
return err
|
|
}
|
|
if err := e.Run(path); err != nil {
|
|
e.Close()
|
|
return err
|
|
}
|
|
|
|
unknown, errs := e.Apply(cfg)
|
|
if logger != nil {
|
|
for _, k := range unknown {
|
|
logger.Warnln("lua: unknown yay.opt key:", k)
|
|
}
|
|
for _, err := range errs {
|
|
logger.Errorln(err)
|
|
}
|
|
}
|
|
|
|
cfg.SetLuaEngine(e)
|
|
return nil
|
|
}
|