Skip to content

Commit 9a4e77e

Browse files
authored
Refactor: Load new template plugins and be compatible with old formats (version-fox#136)
* Refactoring plugin loading mechanism, support for both old and new templates * fix: manifest url * remove author field * Load the corresponding hook when calling * revert * mod
1 parent 5008a7b commit 9a4e77e

File tree

16 files changed

+444
-75
lines changed

16 files changed

+444
-75
lines changed

cmd/commands/info.go

-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ func infoCmd(ctx *cli.Context) error {
4444

4545
pterm.Println("Plugin info:")
4646
pterm.Println("Name ", "->", pterm.LightBlue(source.Name))
47-
pterm.Println("Author ", "->", pterm.LightBlue(source.Author))
4847
pterm.Println("Version ", "->", pterm.LightBlue(source.Version))
4948
pterm.Println("Desc ", "->", pterm.LightBlue(source.Description))
5049
pterm.Println("UpdateUrl", "->", pterm.LightBlue(source.UpdateUrl))

codecov.yml

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
coverage:
2+
status:
3+
project:
4+
default:
5+
target: auto
6+
# allow coverage to drop by this amount and still post success
7+
threshold: 0.5%
8+
if_ci_failed: error
9+
patch: off # no github status notice for coverage of the PR diff.

internal/env/macos_env.go

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ type macosEnvManager struct {
3636

3737
func (m *macosEnvManager) Paths(paths []string) string {
3838
set := util.NewSortedSetWithSlice[string](paths)
39+
// TODO can replace with filepath.ListSeparator
3940
return strings.Join(set.Slice(), ":")
4041
}
4142

internal/interfaces.go

+16-6
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,20 @@ type EnvKeysHookResultItem struct {
160160
}
161161

162162
type LuaPluginInfo struct {
163-
Name string `luai:"name"`
164-
Author string `luai:"author"`
165-
Version string `luai:"version"`
166-
Description string `luai:"description"`
167-
UpdateUrl string `luai:"updateUrl"`
168-
MinRuntimeVersion string `luai:"minRuntimeVersion"`
163+
Name string `luai:"name"`
164+
Version string `luai:"version"`
165+
Description string `luai:"description"`
166+
UpdateUrl string `luai:"updateUrl"` // TODO Will be deprecated in future versions
167+
ManifestUrl string `luai:"manifestUrl"`
168+
Homepage string `luai:"homepage"`
169+
License string `luai:"license"`
170+
MinRuntimeVersion string `luai:"minRuntimeVersion"`
171+
Notes []string `luai:"notes"`
172+
}
173+
174+
// LuaRuntime represents the runtime information of the Lua environment.
175+
type LuaRuntime struct {
176+
OsType string `luai:"osType"`
177+
ArchType string `luai:"archType"`
178+
Version string `luai:"version"`
169179
}

internal/luai/vm.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ package luai
1818

1919
import (
2020
_ "embed"
21-
2221
"github.com/version-fox/vfox/internal/config"
2322
"github.com/version-fox/vfox/internal/module"
2423
lua "github.com/yuin/gopher-lua"
24+
"strings"
2525
)
2626

2727
//go:embed fixtures/preload.lua
@@ -52,6 +52,12 @@ func (vm *LuaVM) Prepare(options *PrepareOptions) error {
5252
return nil
5353
}
5454

55+
// LimitPackagePath limits the package path of the Lua VM.
56+
func (vm *LuaVM) LimitPackagePath(packagePaths ...string) {
57+
packageModule := vm.Instance.GetGlobal("package").(*lua.LTable)
58+
packageModule.RawSetString("path", lua.LString(strings.Join(packagePaths, ";")))
59+
}
60+
5561
func (vm *LuaVM) ReturnedValue() *lua.LTable {
5662
table := vm.Instance.ToTable(-1) // returned value
5763
vm.Instance.Pop(1) // remove received value

internal/manager.go

+11-17
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func (m *Manager) EnvKeys() (*env.Envs, error) {
7171

7272
// LookupSdk lookup sdk by name
7373
func (m *Manager) LookupSdk(name string) (*Sdk, error) {
74-
pluginPath := filepath.Join(m.PathMeta.PluginPath, strings.ToLower(name), "main.lua")
74+
pluginPath := filepath.Join(m.PathMeta.PluginPath, strings.ToLower(name))
7575
if !util.FileExists(pluginPath) {
7676
oldPath := filepath.Join(m.PathMeta.PluginPath, strings.ToLower(name)+".lua")
7777
if !util.FileExists(oldPath) {
@@ -83,15 +83,11 @@ func (m *Manager) LookupSdk(name string) (*Sdk, error) {
8383
if err != nil {
8484
return nil, fmt.Errorf("failed to migrate an old plug-in: %w", err)
8585
}
86-
if err = os.Rename(oldPath, pluginPath); err != nil {
86+
if err = os.Rename(oldPath, filepath.Join(pluginPath, "main.lua")); err != nil {
8787
return nil, fmt.Errorf("failed to migrate an old plug-in: %w", err)
8888
}
8989
}
90-
content, err := m.loadLuaFromFileOrUrl(pluginPath)
91-
if err != nil {
92-
return nil, err
93-
}
94-
luaPlugin, err := NewLuaPlugin(content, pluginPath, m)
90+
luaPlugin, err := NewLuaPlugin(pluginPath, m)
9591
if err != nil {
9692
return nil, err
9793
}
@@ -126,10 +122,9 @@ func (m *Manager) LoadAllSdk() (map[string]*Sdk, error) {
126122
} else {
127123
continue
128124
}
129-
content, _ := m.loadLuaFromFileOrUrl(path)
130-
source, err := NewLuaPlugin(content, path, m)
125+
source, err := NewLuaPlugin(filepath.Dir(path), m)
131126
if err != nil {
132-
pterm.Printf("Failed to load %s plugin, err: %s\n", path, err)
127+
pterm.Printf("Failed to load %s plugin, err: %s\n", filepath.Dir(path), err)
133128
continue
134129
}
135130
sdk, _ := NewSdk(m, source)
@@ -180,21 +175,21 @@ func (m *Manager) Update(pluginName string) error {
180175
if err != nil {
181176
return fmt.Errorf("fetch plugin failed, err: %w", err)
182177
}
183-
source, err := NewLuaPlugin(content, updateUrl, m)
178+
source, err := NewLegacyLuaPlugin(content, updateUrl, m)
184179
if err != nil {
185180
return fmt.Errorf("check %s plugin failed, err: %w", updateUrl, err)
186181
}
187182
success := false
188-
backupPath := sdk.Plugin.Filepath + ".bak"
189-
err = util.CopyFile(sdk.Plugin.Filepath, backupPath)
183+
backupPath := sdk.Plugin.Path + ".bak"
184+
err = util.CopyFile(sdk.Plugin.Path, backupPath)
190185
if err != nil {
191186
return fmt.Errorf("backup %s plugin failed, err: %w", updateUrl, err)
192187
}
193188
defer func() {
194189
if success {
195190
_ = os.Remove(backupPath)
196191
} else {
197-
_ = os.Rename(backupPath, sdk.Plugin.Filepath)
192+
_ = os.Rename(backupPath, sdk.Plugin.Path)
198193
}
199194
}()
200195
pterm.Println("Checking plugin version...")
@@ -203,7 +198,7 @@ func (m *Manager) Update(pluginName string) error {
203198
pterm.Printf("the plugin is already the latest version")
204199
return nil
205200
}
206-
err = os.WriteFile(sdk.Plugin.Filepath, []byte(content), 0644)
201+
err = os.WriteFile(sdk.Plugin.Path, []byte(content), 0644)
207202
if err != nil {
208203
return fmt.Errorf("update %s plugin failed: %w", updateUrl, err)
209204
}
@@ -243,7 +238,7 @@ func (m *Manager) Add(pluginName, url, alias string) error {
243238
return fmt.Errorf("failed to load plugin: %w", err)
244239
}
245240
pterm.Println("Checking plugin...")
246-
source, err := NewLuaPlugin(content, url, m)
241+
source, err := NewLegacyLuaPlugin(content, url, m)
247242
if err != nil {
248243
return fmt.Errorf("check plugin error: %w", err)
249244
}
@@ -270,7 +265,6 @@ func (m *Manager) Add(pluginName, url, alias string) error {
270265
}
271266
pterm.Println("Plugin info:")
272267
pterm.Println("Name ", "->", pterm.LightBlue(source.Name))
273-
pterm.Println("Author ", "->", pterm.LightBlue(source.Author))
274268
pterm.Println("Version", "->", pterm.LightBlue(source.Version))
275269
pterm.Println("Desc ", "->", pterm.LightBlue(source.Description))
276270
pterm.Println("Path ", "->", pterm.LightBlue(destPath))

internal/plugin.go

+136-25
Original file line numberDiff line numberDiff line change
@@ -30,36 +30,46 @@ import (
3030
)
3131

3232
const (
33-
LuaPluginObjKey = "PLUGIN"
34-
OsType = "OS_TYPE"
35-
ArchType = "ARCH_TYPE"
33+
luaPluginObjKey = "PLUGIN"
34+
osType = "OS_TYPE"
35+
archType = "ARCH_TYPE"
36+
runtime = "RUNTIME"
37+
)
38+
39+
type HookFunc struct {
40+
Name string
41+
Required bool
42+
Filename string
43+
}
44+
45+
var (
46+
// HookFuncMap is a map of built-in hook functions.
47+
HookFuncMap = map[string]HookFunc{
48+
"Available": {Name: "Available", Required: true, Filename: "available"},
49+
"PreInstall": {Name: "PreInstall", Required: true, Filename: "pre_install"},
50+
"EnvKeys": {Name: "EnvKeys", Required: true, Filename: "env_keys"},
51+
"PostInstall": {Name: "PostInstall", Required: false, Filename: "post_install"},
52+
"PreUse": {Name: "PreUse", Required: false, Filename: "pre_use"},
53+
}
3654
)
3755

3856
type LuaPlugin struct {
3957
vm *luai.LuaVM
4058
pluginObj *lua.LTable
4159
// plugin source path
42-
Filepath string
60+
Path string
4361
// plugin filename, this is also alias name, sdk-name
4462
SdkName string
45-
// The name defined inside the plugin
46-
4763
LuaPluginInfo
4864
}
4965

50-
func (l *LuaPlugin) checkValid() error {
51-
if l.vm == nil || l.vm.Instance == nil {
52-
return fmt.Errorf("lua vm is nil")
53-
}
54-
55-
if !l.HasFunction("Available") {
56-
return fmt.Errorf("[Available] function not found")
57-
}
58-
if !l.HasFunction("PreInstall") {
59-
return fmt.Errorf("[PreInstall] function not found")
60-
}
61-
if !l.HasFunction("EnvKeys") {
62-
return fmt.Errorf("[EnvKeys] function not found")
66+
func (l *LuaPlugin) Validate() error {
67+
for _, hf := range HookFuncMap {
68+
if hf.Required {
69+
if !l.HasFunction(hf.Name) {
70+
return fmt.Errorf("[%s] function not found", hf.Name)
71+
}
72+
}
6373
}
6474
return nil
6575
}
@@ -322,7 +332,9 @@ func (l *LuaPlugin) CallFunction(funcName string, args ...lua.LValue) error {
322332
return nil
323333
}
324334

325-
func NewLuaPlugin(content, path string, manager *Manager) (*LuaPlugin, error) {
335+
// NewLegacyLuaPlugin creates a new LuaPlugin instance from old plugin format.
336+
// TODO This will be deprecated in future versions.
337+
func NewLegacyLuaPlugin(content, path string, manager *Manager) (*LuaPlugin, error) {
326338
vm := luai.NewLuaVM()
327339

328340
if err := vm.Prepare(&luai.PrepareOptions{
@@ -337,10 +349,10 @@ func NewLuaPlugin(content, path string, manager *Manager) (*LuaPlugin, error) {
337349

338350
// !!!! Must be set after loading the script to prevent overwriting!
339351
// set OS_TYPE and ARCH_TYPE
340-
vm.Instance.SetGlobal(OsType, lua.LString(util.GetOSType()))
341-
vm.Instance.SetGlobal(ArchType, lua.LString(util.GetArchType()))
352+
vm.Instance.SetGlobal(osType, lua.LString(util.GetOSType()))
353+
vm.Instance.SetGlobal(archType, lua.LString(util.GetArchType()))
342354

343-
pluginObj := vm.Instance.GetGlobal(LuaPluginObjKey)
355+
pluginObj := vm.Instance.GetGlobal(luaPluginObjKey)
344356
if pluginObj.Type() == lua.LTNil {
345357
return nil, fmt.Errorf("plugin object not found")
346358
}
@@ -350,11 +362,11 @@ func NewLuaPlugin(content, path string, manager *Manager) (*LuaPlugin, error) {
350362
source := &LuaPlugin{
351363
vm: vm,
352364
pluginObj: PLUGIN,
353-
Filepath: path,
365+
Path: path,
354366
SdkName: filepath.Base(filepath.Dir(path)),
355367
}
356368

357-
if err := source.checkValid(); err != nil {
369+
if err := source.Validate(); err != nil {
358370
return nil, err
359371
}
360372

@@ -376,6 +388,105 @@ func NewLuaPlugin(content, path string, manager *Manager) (*LuaPlugin, error) {
376388
return source, nil
377389
}
378390

391+
// NewLuaPlugin creates a new LuaPlugin instance from the specified directory path.
392+
// The plugin directory must meet one of the following conditions:
393+
// - The directory must contain a metadata.lua file and a hooks directory that includes all must be implemented hook functions.
394+
// - The directory contain a main.lua file that defines the plugin object and all hook functions.
395+
func NewLuaPlugin(pluginDirPath string, manager *Manager) (*LuaPlugin, error) {
396+
vm := luai.NewLuaVM()
397+
398+
if err := vm.Prepare(&luai.PrepareOptions{
399+
Config: manager.Config,
400+
}); err != nil {
401+
return nil, err
402+
}
403+
404+
mainPath := filepath.Join(pluginDirPath, "main.lua")
405+
// main.lua first
406+
if util.FileExists(mainPath) {
407+
vm.LimitPackagePath(filepath.Join(pluginDirPath, "?.lua"))
408+
if err := vm.Instance.DoFile(mainPath); err != nil {
409+
return nil, err
410+
}
411+
} else {
412+
// Limit package search scope, hooks directory search priority is higher than lib directory
413+
hookPath := filepath.Join(pluginDirPath, "hooks", "?.lua")
414+
libPath := filepath.Join(pluginDirPath, "lib", "?.lua")
415+
vm.LimitPackagePath(hookPath, libPath)
416+
417+
// load metadata file
418+
metadataPath := filepath.Join(pluginDirPath, "metadata.lua")
419+
if !util.FileExists(metadataPath) {
420+
return nil, fmt.Errorf("plugin invalid, metadata file not found")
421+
}
422+
423+
if err := vm.Instance.DoFile(metadataPath); err != nil {
424+
return nil, fmt.Errorf("failed to load meatadata file, %w", err)
425+
}
426+
427+
// load hook func files
428+
for _, hf := range HookFuncMap {
429+
hp := filepath.Join(pluginDirPath, "hooks", hf.Filename+".lua")
430+
431+
if !hf.Required && !util.FileExists(hp) {
432+
continue
433+
}
434+
if err := vm.Instance.DoFile(hp); err != nil {
435+
return nil, fmt.Errorf("failed to load [%s] hook function: %s", hf.Name, err.Error())
436+
}
437+
}
438+
}
439+
440+
// !!!! Must be set after loading the script to prevent overwriting!
441+
// set OS_TYPE and ARCH_TYPE
442+
vm.Instance.SetGlobal(osType, lua.LString(util.GetOSType()))
443+
vm.Instance.SetGlobal(archType, lua.LString(util.GetArchType()))
444+
445+
r, err := luai.Marshal(vm.Instance, LuaRuntime{
446+
OsType: string(util.GetOSType()),
447+
ArchType: string(util.GetArchType()),
448+
Version: RuntimeVersion,
449+
})
450+
if err != nil {
451+
return nil, err
452+
}
453+
vm.Instance.SetGlobal(runtime, r)
454+
455+
pluginObj := vm.Instance.GetGlobal(luaPluginObjKey)
456+
if pluginObj.Type() == lua.LTNil {
457+
return nil, fmt.Errorf("plugin object not found")
458+
}
459+
460+
PLUGIN := pluginObj.(*lua.LTable)
461+
462+
source := &LuaPlugin{
463+
vm: vm,
464+
pluginObj: PLUGIN,
465+
Path: pluginDirPath,
466+
SdkName: filepath.Base(pluginDirPath),
467+
}
468+
469+
if err = source.Validate(); err != nil {
470+
return nil, err
471+
}
472+
473+
pluginInfo := LuaPluginInfo{}
474+
if err = luai.Unmarshal(PLUGIN, &pluginInfo); err != nil {
475+
return nil, err
476+
}
477+
478+
source.LuaPluginInfo = pluginInfo
479+
480+
if !isValidName(source.Name) {
481+
return nil, fmt.Errorf("invalid plugin name")
482+
}
483+
484+
if source.Name == "" {
485+
return nil, fmt.Errorf("no plugin name provided")
486+
}
487+
return source, nil
488+
}
489+
379490
func isValidName(name string) bool {
380491
// The regular expression means: start with a letter,
381492
// followed by any number of letters, digits, or underscores.

0 commit comments

Comments
 (0)