13 files changed
+525
-53
No files matched your search
+4
-1
@@ -31,4 +31,7 @@ qemu-*
|
||||
|
||||
node_modules/
|
||||
xgotext
|
||||
.devcontainer/
|
||||
.devcontainer/
|
||||
init.lua
|
||||
dyalpm/
|
||||
!doc/init.lua
|
||||
@@ -0,0 +1,53 @@
|
||||
-- Example yay init.lua
|
||||
--
|
||||
-- This file is a complete template for yay.opt. Copy entries you need,
|
||||
-- or keep all of them and tune values. Command-line flags still override
|
||||
-- these values.
|
||||
|
||||
-- Strings
|
||||
yay.opt.aururl = "https://aur.archlinux.org" -- Base AUR URL.
|
||||
yay.opt.aurrpcurl = "https://aur.archlinux.org/rpc?" -- AUR RPC endpoint URL.
|
||||
yay.opt.build_dir = os.getenv("HOME") .. "/.cache/yay" -- Build/cache directory for AUR packages.
|
||||
yay.opt.editor = os.getenv("EDITOR") or os.getenv("VISUAL") or "vi" -- Editor command used for PKGBUILD edits; empty uses VISUAL/EDITOR.
|
||||
yay.opt.editor_flags = "" -- Extra flags passed to the editor command.
|
||||
yay.opt.makepkg_bin = "makepkg" -- makepkg executable (name in PATH or absolute path).
|
||||
yay.opt.makepkg_conf = "" -- makepkg.conf path; empty uses default makepkg config.
|
||||
yay.opt.pacman_bin = "pacman" -- pacman executable.
|
||||
yay.opt.pacman_conf = "/etc/pacman.conf" -- pacman.conf file path.
|
||||
yay.opt.redownload = "no" -- PKGBUILD download mode: "no" | "yes" | "all".
|
||||
yay.opt.git_bin = "git" -- git executable.
|
||||
yay.opt.gpg_bin = "gpg" -- gpg executable.
|
||||
yay.opt.gpg_flags = "" -- Extra flags passed to gpg.
|
||||
yay.opt.mflags = "" -- Extra flags passed to makepkg.
|
||||
yay.opt.sort_by = "" -- AUR search sort field: "votes" | "popularity" | "name" | "base" | "submitted" | "modified" | "".
|
||||
yay.opt.search_by = "name-desc" -- AUR search field: "name" | "name-desc" | "maintainer" | "depends" | "checkdepends" | "makedepends" | "optdepends" | "provides" | "conflicts" | "replaces" | "groups" | "keywords" | "comaintainers".
|
||||
yay.opt.git_flags = "" -- Extra flags passed to git.
|
||||
yay.opt.remove_make = "ask" -- Remove makedepends mode: "no" | "yes" | "ask" | "askyes".
|
||||
yay.opt.sudo_bin = "sudo" -- Privilege elevation command.
|
||||
yay.opt.sudo_flags = "" -- Extra flags passed to the sudo command.
|
||||
yay.opt.rebuild = "no" -- Build mode: "no" | "yes" | "tree" | "all".
|
||||
|
||||
-- Integers
|
||||
yay.opt.request_split_n = 150 -- Max packages per AUR RPC request (use values > 0).
|
||||
yay.opt.completion_refresh_time = 7 -- Completion cache refresh days: -1 (never), 0 (always), >0 (every N days).
|
||||
yay.opt.max_concurrent_downloads = 1 -- Parallel PKGBUILD source downloads; 0 uses CPU count.
|
||||
|
||||
-- Booleans
|
||||
yay.opt.bottom_up = true -- Show AUR packages before repo packages in mixed results.
|
||||
yay.opt.sudo_loop = false -- Keep sudo session alive in the background during long builds.
|
||||
yay.opt.devel = false -- Check development/VCS packages on sysupgrade.
|
||||
yay.opt.clean_after = false -- Remove untracked files after install.
|
||||
yay.opt.keep_src = false -- Keep pkg/ and src/ after successful builds.
|
||||
yay.opt.provides = true -- Resolve matching providers when dependencies are ambiguous.
|
||||
yay.opt.pgp_fetch = true -- Prompt to import unknown PGP keys from validpgpkeys.
|
||||
yay.opt.clean_menu = true -- Show pre-build clean menu.
|
||||
yay.opt.diff_menu = true -- Show diff menu before building.
|
||||
yay.opt.edit_menu = false -- Show PKGBUILD edit menu before building.
|
||||
yay.opt.combined_upgrade = true -- Use combined repo+AUR upgrade flow on sysupgrade.
|
||||
yay.opt.use_ask = false -- Use pacman's --ask to auto-confirm known conflicts.
|
||||
yay.opt.batch_install = false -- Queue AUR package installs instead of installing each package immediately.
|
||||
yay.opt.single_line_results = false -- Use single-line search result format.
|
||||
yay.opt.separate_sources = true -- Separate query results by source (repo vs AUR).
|
||||
yay.opt.debug = false -- Enable debug logging and local init.lua lookup convenience.
|
||||
yay.opt.rpc = true -- Use AUR RPC for dependency/query operations.
|
||||
yay.opt.double_confirm = true -- Ask for confirmation before and after builds during upgrades.
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
# Lua configuration (`init.lua`)
|
||||
|
||||
yay can optionally load a Lua configuration file, `init.lua`. `init.lua` overlays whatever is in `config.json`, and any command-line flag you pass still wins over `init.lua`.
|
||||
|
||||
## Location
|
||||
|
||||
`init.lua` is looked up, in order:
|
||||
|
||||
1. `$XDG_CONFIG_HOME/yay/init.lua`
|
||||
2. `$HOME/.config/yay/init.lua`
|
||||
|
||||
## Setting options with `yay.opt`
|
||||
|
||||
Assign to keys on the `yay.opt` table using the exact option names shown
|
||||
below.
|
||||
|
||||
```lua
|
||||
yay.opt.bottom_up = false
|
||||
yay.opt.clean_after = true
|
||||
yay.opt.sort_by = "votes"
|
||||
yay.opt.request_split_n = 150
|
||||
yay.opt.editor = os.getenv("EDITOR") or "vi"
|
||||
```
|
||||
|
||||
Unknown keys and values of the wrong type are treated as errors. yay aborts
|
||||
startup and reports the offending keys/values so misconfigurations fail fast.
|
||||
|
||||
### Available options
|
||||
|
||||
**Strings**
|
||||
|
||||
`aururl`, `aurrpcurl`, `build_dir`, `editor`, `editor_flags`, `makepkg_bin`,
|
||||
`makepkg_conf`, `pacman_bin`, `pacman_conf`, `redownload`, `rebuild`, `git_bin`,
|
||||
`gpg_bin`, `gpg_flags`, `mflags`, `sort_by`, `search_by`, `git_flags`,
|
||||
`remove_make`, `sudo_bin`, `sudo_flags`
|
||||
|
||||
**Integers**
|
||||
|
||||
`request_split_n`, `completion_refresh_time`, `max_concurrent_downloads`
|
||||
|
||||
**Booleans**
|
||||
|
||||
`bottom_up`, `sudo_loop`, `devel`, `clean_after`, `keep_src`, `provides`,
|
||||
`pgp_fetch`, `clean_menu`, `diff_menu`, `edit_menu`, `combined_upgrade`,
|
||||
`use_ask`, `batch_install`, `single_line_results`, `separate_sources`, `debug`,
|
||||
`rpc`, `double_confirm`
|
||||
|
||||
A ready-to-copy example
|
||||
lives at [`doc/init.lua`](init.lua).
|
||||
@@ -12,6 +12,7 @@ require (
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/leonelquinteros/gotext v1.7.2
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/yuin/gopher-lua v1.1.2
|
||||
golang.org/x/net v0.55.0
|
||||
golang.org/x/sys v0.45.0
|
||||
golang.org/x/term v0.43.0
|
||||
|
||||
@@ -57,6 +57,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/yuin/gopher-lua v1.1.2 h1:yF/FjE3hD65tBbt0VXLE13HWS9h34fdzJmrWRXwobGA=
|
||||
github.com/yuin/gopher-lua v1.1.2/go.mod h1:7aRmXIWl37SqRf0koeyylBEzJ+aPt8A+mmkQ4f1ntR8=
|
||||
go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU=
|
||||
go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/Jguer/yay/v12/pkg/db/ialpm"
|
||||
"github.com/Jguer/yay/v12/pkg/runtime"
|
||||
"github.com/Jguer/yay/v12/pkg/settings"
|
||||
"github.com/Jguer/yay/v12/pkg/settings/lua"
|
||||
"github.com/Jguer/yay/v12/pkg/settings/parser"
|
||||
"github.com/Jguer/yay/v12/pkg/text"
|
||||
)
|
||||
@@ -85,6 +86,15 @@ func main() {
|
||||
fallbackLog.Errorln(errS)
|
||||
}
|
||||
|
||||
if luaPath := settings.GetLuaConfigPath(cfg.Debug); luaPath != "" {
|
||||
if errLua := lua.LoadInto(fallbackLog, luaPath, cfg); errLua != nil {
|
||||
fallbackLog.Errorln(errLua)
|
||||
ret = 1
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
cmdArgs := parser.MakeArguments()
|
||||
|
||||
// Parse command line
|
||||
|
||||
+51
-51
@@ -23,59 +23,59 @@ var NoConfirm = false
|
||||
|
||||
// Configuration stores yay's config.
|
||||
type Configuration struct {
|
||||
AURURL string `json:"aururl"`
|
||||
AURRPCURL string `json:"aurrpcurl"`
|
||||
BuildDir string `json:"buildDir"`
|
||||
Editor string `json:"editor"`
|
||||
EditorFlags string `json:"editorflags"`
|
||||
MakepkgBin string `json:"makepkgbin"`
|
||||
MakepkgConf string `json:"makepkgconf"`
|
||||
PacmanBin string `json:"pacmanbin"`
|
||||
PacmanConf string `json:"pacmanconf"`
|
||||
ReDownload string `json:"redownload"`
|
||||
AnswerClean string `json:"answerclean"`
|
||||
AnswerDiff string `json:"answerdiff"`
|
||||
AnswerEdit string `json:"answeredit"`
|
||||
AnswerUpgrade string `json:"answerupgrade"`
|
||||
GitBin string `json:"gitbin"`
|
||||
GpgBin string `json:"gpgbin"`
|
||||
GpgFlags string `json:"gpgflags"`
|
||||
MFlags string `json:"mflags"`
|
||||
SortBy string `json:"sortby"`
|
||||
SearchBy string `json:"searchby"`
|
||||
GitFlags string `json:"gitflags"`
|
||||
RemoveMake string `json:"removemake"`
|
||||
SudoBin string `json:"sudobin"`
|
||||
SudoFlags string `json:"sudoflags"`
|
||||
Version string `json:"version"`
|
||||
RequestSplitN int `json:"requestsplitn"`
|
||||
CompletionInterval int `json:"completionrefreshtime"`
|
||||
MaxConcurrentDownloads int `json:"maxconcurrentdownloads"`
|
||||
BottomUp bool `json:"bottomup"`
|
||||
SudoLoop bool `json:"sudoloop"`
|
||||
Devel bool `json:"devel"`
|
||||
CleanAfter bool `json:"cleanAfter"`
|
||||
KeepSrc bool `json:"keepSrc"`
|
||||
Provides bool `json:"provides"`
|
||||
PGPFetch bool `json:"pgpfetch"`
|
||||
CleanMenu bool `json:"cleanmenu"`
|
||||
DiffMenu bool `json:"diffmenu"`
|
||||
EditMenu bool `json:"editmenu"`
|
||||
CombinedUpgrade bool `json:"combinedupgrade"`
|
||||
UseAsk bool `json:"useask"`
|
||||
BatchInstall bool `json:"batchinstall"`
|
||||
SingleLineResults bool `json:"singlelineresults"`
|
||||
SeparateSources bool `json:"separatesources"`
|
||||
Debug bool `json:"debug"`
|
||||
UseRPC bool `json:"rpc"`
|
||||
DoubleConfirm bool `json:"doubleconfirm"` // confirm install before and after build
|
||||
AURURL string `json:"aururl" lua:"aururl"`
|
||||
AURRPCURL string `json:"aurrpcurl" lua:"aurrpcurl"`
|
||||
BuildDir string `json:"buildDir" lua:"build_dir"`
|
||||
Editor string `json:"editor" lua:"editor"`
|
||||
EditorFlags string `json:"editorflags" lua:"editor_flags"`
|
||||
MakepkgBin string `json:"makepkgbin" lua:"makepkg_bin"`
|
||||
MakepkgConf string `json:"makepkgconf" lua:"makepkg_conf"`
|
||||
PacmanBin string `json:"pacmanbin" lua:"pacman_bin"`
|
||||
PacmanConf string `json:"pacmanconf" lua:"pacman_conf"`
|
||||
ReDownload string `json:"redownload" lua:"redownload"`
|
||||
AnswerClean string `json:"answerclean" lua:"-"`
|
||||
AnswerDiff string `json:"answerdiff" lua:"-"`
|
||||
AnswerEdit string `json:"answeredit" lua:"-"`
|
||||
AnswerUpgrade string `json:"answerupgrade" lua:"-"`
|
||||
GitBin string `json:"gitbin" lua:"git_bin"`
|
||||
GpgBin string `json:"gpgbin" lua:"gpg_bin"`
|
||||
GpgFlags string `json:"gpgflags" lua:"gpg_flags"`
|
||||
MFlags string `json:"mflags" lua:"mflags"`
|
||||
SortBy string `json:"sortby" lua:"sort_by"`
|
||||
SearchBy string `json:"searchby" lua:"search_by"`
|
||||
GitFlags string `json:"gitflags" lua:"git_flags"`
|
||||
RemoveMake string `json:"removemake" lua:"remove_make"`
|
||||
SudoBin string `json:"sudobin" lua:"sudo_bin"`
|
||||
SudoFlags string `json:"sudoflags" lua:"sudo_flags"`
|
||||
Version string `json:"version" lua:"-"`
|
||||
RequestSplitN int `json:"requestsplitn" lua:"request_split_n"`
|
||||
CompletionInterval int `json:"completionrefreshtime" lua:"completion_refresh_time"`
|
||||
MaxConcurrentDownloads int `json:"maxconcurrentdownloads" lua:"max_concurrent_downloads"`
|
||||
BottomUp bool `json:"bottomup" lua:"bottom_up"`
|
||||
SudoLoop bool `json:"sudoloop" lua:"sudo_loop"`
|
||||
Devel bool `json:"devel" lua:"devel"`
|
||||
CleanAfter bool `json:"cleanAfter" lua:"clean_after"`
|
||||
KeepSrc bool `json:"keepSrc" lua:"keep_src"`
|
||||
Provides bool `json:"provides" lua:"provides"`
|
||||
PGPFetch bool `json:"pgpfetch" lua:"pgp_fetch"`
|
||||
CleanMenu bool `json:"cleanmenu" lua:"clean_menu"`
|
||||
DiffMenu bool `json:"diffmenu" lua:"diff_menu"`
|
||||
EditMenu bool `json:"editmenu" lua:"edit_menu"`
|
||||
CombinedUpgrade bool `json:"combinedupgrade" lua:"combined_upgrade"`
|
||||
UseAsk bool `json:"useask" lua:"use_ask"`
|
||||
BatchInstall bool `json:"batchinstall" lua:"batch_install"`
|
||||
SingleLineResults bool `json:"singlelineresults" lua:"single_line_results"`
|
||||
SeparateSources bool `json:"separatesources" lua:"separate_sources"`
|
||||
Debug bool `json:"debug" lua:"debug"`
|
||||
UseRPC bool `json:"rpc" lua:"rpc"`
|
||||
DoubleConfirm bool `json:"doubleconfirm" lua:"double_confirm"` // confirm install before and after build
|
||||
|
||||
CompletionPath string `json:"-"`
|
||||
VCSFilePath string `json:"-"`
|
||||
CompletionPath string `json:"-" lua:"-"`
|
||||
VCSFilePath string `json:"-" lua:"-"`
|
||||
// ConfigPath string `json:"-"`
|
||||
SaveConfig bool `json:"-"`
|
||||
Mode parser.TargetMode `json:"-"`
|
||||
ReBuild parser.RebuildMode `json:"rebuild"`
|
||||
SaveConfig bool `json:"-" lua:"-"`
|
||||
Mode parser.TargetMode `json:"-" lua:"-"`
|
||||
ReBuild parser.RebuildMode `json:"rebuild" lua:"rebuild"`
|
||||
}
|
||||
|
||||
// SaveConfig writes yay config to file.
|
||||
|
||||
+27
-1
@@ -7,7 +7,8 @@ import (
|
||||
|
||||
const (
|
||||
configFileName string = "config.json" // configFileName holds the name of the config file.
|
||||
vcsFileName string = "vcs.json" // vcsFileName holds the name of the vcs file.
|
||||
luaConfigFileName string = "init.lua"
|
||||
vcsFileName string = "vcs.json" // vcsFileName holds the name of the vcs file.
|
||||
completionFileName string = "completion.cache"
|
||||
systemdCache string = "/var/cache/yay" // systemd should handle cache creation
|
||||
)
|
||||
@@ -30,6 +31,31 @@ func GetConfigPath() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetLuaConfigPath returns the first configured init.lua path that exists.
|
||||
func GetLuaConfigPath(debug bool) string {
|
||||
var candidates []string
|
||||
|
||||
if debug {
|
||||
candidates = append(candidates, luaConfigFileName)
|
||||
}
|
||||
|
||||
if configHome := os.Getenv("XDG_CONFIG_HOME"); configHome != "" {
|
||||
candidates = append(candidates, filepath.Join(configHome, "yay", luaConfigFileName))
|
||||
}
|
||||
|
||||
if configHome := os.Getenv("HOME"); configHome != "" {
|
||||
candidates = append(candidates, filepath.Join(configHome, ".config", "yay", luaConfigFileName))
|
||||
}
|
||||
|
||||
for _, candidate := range candidates {
|
||||
if _, err := os.Stat(candidate); err == nil {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func getCacheHome() (string, error) {
|
||||
uid := os.Geteuid()
|
||||
|
||||
|
||||
@@ -29,3 +29,29 @@ func Test_getCacheHome(t *testing.T) {
|
||||
require.NoError(t, os.Unsetenv("TMPDIR"))
|
||||
require.NoError(t, os.Unsetenv("SUDO_USER"))
|
||||
}
|
||||
|
||||
func TestGetLuaConfigPath(t *testing.T) {
|
||||
configHome := t.TempDir()
|
||||
t.Setenv("XDG_CONFIG_HOME", configHome)
|
||||
require.NoError(t, os.Unsetenv("HOME"))
|
||||
|
||||
assert.Empty(t, GetLuaConfigPath(false))
|
||||
|
||||
luaDir := filepath.Join(configHome, "yay")
|
||||
require.NoError(t, os.MkdirAll(luaDir, 0o755))
|
||||
luaPath := filepath.Join(luaDir, "init.lua")
|
||||
require.NoError(t, os.WriteFile(luaPath, []byte("-- test"), 0o600))
|
||||
|
||||
assert.Equal(t, luaPath, GetLuaConfigPath(false))
|
||||
}
|
||||
|
||||
func TestGetLuaConfigPathDebugCwd(t *testing.T) {
|
||||
require.NoError(t, os.Unsetenv("XDG_CONFIG_HOME"))
|
||||
require.NoError(t, os.Unsetenv("HOME"))
|
||||
|
||||
t.Chdir(t.TempDir())
|
||||
require.NoError(t, os.WriteFile("init.lua", []byte("-- test"), 0o600))
|
||||
|
||||
assert.Equal(t, "init.lua", GetLuaConfigPath(true))
|
||||
assert.Empty(t, GetLuaConfigPath(false))
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package lua
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Jguer/yay/v12/pkg/multierror"
|
||||
"github.com/Jguer/yay/v12/pkg/text"
|
||||
)
|
||||
|
||||
// LoadInto applies the yay.opt values from path onto cfg.
|
||||
func LoadInto(_ *text.Logger, path string, cfg any) error {
|
||||
engine := New()
|
||||
defer engine.Close()
|
||||
|
||||
if err := engine.L.DoFile(path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
unknown, errs := engine.Apply(cfg)
|
||||
|
||||
if len(unknown) == 0 && len(errs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
merr := &multierror.MultiError{}
|
||||
|
||||
for _, key := range unknown {
|
||||
merr.Add(fmt.Errorf("init.lua: unknown yay.opt key: %s", key))
|
||||
}
|
||||
|
||||
for _, err := range errs {
|
||||
merr.Add(fmt.Errorf("init.lua: %w", err))
|
||||
}
|
||||
|
||||
return merr.Return()
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package lua
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func writeLuaFile(t *testing.T, body string) string {
|
||||
t.Helper()
|
||||
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "init.lua")
|
||||
require.NoError(t, os.WriteFile(path, []byte(body), 0o600))
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
func TestLoadIntoStrictFailsOnUnknownKey(t *testing.T) {
|
||||
path := writeLuaFile(t, `
|
||||
yay.opt.unknown_key = true
|
||||
`)
|
||||
|
||||
cfg := &testConfig{}
|
||||
err := LoadInto(nil, path, cfg)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "unknown yay.opt key: unknown_key")
|
||||
}
|
||||
|
||||
func TestLoadIntoStrictFailsOnTypeMismatch(t *testing.T) {
|
||||
path := writeLuaFile(t, `
|
||||
yay.opt.devel = "true"
|
||||
`)
|
||||
|
||||
cfg := &testConfig{}
|
||||
err := LoadInto(nil, path, cfg)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "yay.opt.devel: expected boolean")
|
||||
}
|
||||
|
||||
func TestLoadIntoAppliesValidValues(t *testing.T) {
|
||||
path := writeLuaFile(t, `
|
||||
yay.opt.build_dir = "/tmp/yay"
|
||||
yay.opt.request_split_n = 123
|
||||
yay.opt.devel = true
|
||||
`)
|
||||
|
||||
cfg := &testConfig{}
|
||||
err := LoadInto(nil, path, cfg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "/tmp/yay", cfg.BuildDir)
|
||||
require.Equal(t, 123, cfg.RequestSplitN)
|
||||
require.True(t, cfg.Devel)
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
// Package lua loads yay's optional init.lua configuration.
|
||||
package lua
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
const (
|
||||
globalName = "yay"
|
||||
optTableName = "opt"
|
||||
)
|
||||
|
||||
type Engine struct {
|
||||
L *lua.LState
|
||||
}
|
||||
|
||||
func New() *Engine {
|
||||
state := lua.NewState()
|
||||
|
||||
yayTbl := state.NewTable()
|
||||
state.SetGlobal(globalName, yayTbl)
|
||||
state.SetField(yayTbl, optTableName, state.NewTable())
|
||||
|
||||
return &Engine{L: state}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package lua
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testConfig struct {
|
||||
BuildDir string `json:"buildDir" lua:"build_dir"`
|
||||
RequestSplitN int `json:"requestsplitn" lua:"request_split_n"`
|
||||
Devel bool `json:"devel" lua:"devel"`
|
||||
AnswerClean string `json:"answerclean"`
|
||||
Ignored string `json:"-" lua:"-"`
|
||||
}
|
||||
|
||||
func TestApply(t *testing.T) {
|
||||
e := New()
|
||||
defer e.Close()
|
||||
|
||||
require.NoError(t, e.L.DoString(`
|
||||
yay.opt.build_dir = "/tmp/yay"
|
||||
yay.opt.request_split_n = 200
|
||||
yay.opt.devel = true
|
||||
`))
|
||||
|
||||
cfg := &testConfig{}
|
||||
unknown, errs := e.Apply(cfg)
|
||||
|
||||
assert.Empty(t, unknown)
|
||||
assert.Empty(t, errs)
|
||||
assert.Equal(t, "/tmp/yay", cfg.BuildDir)
|
||||
assert.Equal(t, 200, cfg.RequestSplitN)
|
||||
assert.True(t, cfg.Devel)
|
||||
}
|
||||
|
||||
func TestApplyUnknownAndTypeMismatch(t *testing.T) {
|
||||
e := New()
|
||||
defer e.Close()
|
||||
|
||||
require.NoError(t, e.L.DoString(`
|
||||
yay.opt.does_not_exist = "x"
|
||||
yay.opt.buildDir = "/tmp/nope"
|
||||
yay.opt.requestsplitn = 200
|
||||
yay.opt.Devel = true
|
||||
yay.opt.devel = "not a bool"
|
||||
yay.opt.build_dir = "/tmp/ok"
|
||||
`))
|
||||
|
||||
cfg := &testConfig{}
|
||||
unknown, errs := e.Apply(cfg)
|
||||
|
||||
assert.ElementsMatch(t, []string{"does_not_exist", "buildDir", "requestsplitn", "Devel"}, unknown)
|
||||
assert.Len(t, errs, 1)
|
||||
assert.Zero(t, cfg.RequestSplitN)
|
||||
assert.False(t, cfg.Devel)
|
||||
assert.Equal(t, "/tmp/ok", cfg.BuildDir)
|
||||
}
|
||||
|
||||
func TestApplyIgnoresAnswerOptionsWithoutLuaTags(t *testing.T) {
|
||||
e := New()
|
||||
defer e.Close()
|
||||
|
||||
require.NoError(t, e.L.DoString(`
|
||||
yay.opt.answer_clean = "All"
|
||||
`))
|
||||
|
||||
cfg := &testConfig{}
|
||||
unknown, errs := e.Apply(cfg)
|
||||
|
||||
assert.Equal(t, []string{"answer_clean"}, unknown)
|
||||
assert.Empty(t, errs)
|
||||
assert.Empty(t, cfg.AnswerClean)
|
||||
}
|
||||
|
||||
func TestApplyRejectsNonPointer(t *testing.T) {
|
||||
e := New()
|
||||
defer e.Close()
|
||||
|
||||
_, errs := e.Apply(testConfig{})
|
||||
assert.Len(t, errs, 1)
|
||||
}
|
||||
Reference in new issue
Block a user