mirror of https://github.com/jlelse/GoBlog
118 lines
2.7 KiB
Go
118 lines
2.7 KiB
Go
package plugins
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/traefik/yaegi/interp"
|
|
"github.com/traefik/yaegi/stdlib"
|
|
)
|
|
|
|
// PluginHost manages the plugins.
|
|
type PluginHost struct {
|
|
plugins map[string][]*plugin
|
|
pluginTypes map[string]reflect.Type
|
|
symbols interp.Exports
|
|
embeddedPlugins fs.FS
|
|
}
|
|
|
|
// PluginConfig is the configuration of the plugin.
|
|
type PluginConfig struct {
|
|
// Path is the storage path of the plugin.
|
|
Path string
|
|
// ImportPath is the module path i.e. "github.com/user/module".
|
|
ImportPath string
|
|
}
|
|
|
|
type plugin struct {
|
|
Config *PluginConfig
|
|
plugin any
|
|
}
|
|
|
|
const (
|
|
embeddedPrefix = "embedded:"
|
|
)
|
|
|
|
// NewPluginHost initializes a PluginHost.
|
|
func NewPluginHost(pluginTypes map[string]reflect.Type, symbols interp.Exports, embeddedPlugins fs.FS) *PluginHost {
|
|
return &PluginHost{
|
|
plugins: map[string][]*plugin{},
|
|
pluginTypes: pluginTypes,
|
|
symbols: symbols,
|
|
embeddedPlugins: embeddedPlugins,
|
|
}
|
|
}
|
|
|
|
// LoadPlugin loads a new plugin to the host.
|
|
func (h *PluginHost) LoadPlugin(config *PluginConfig) (map[string]any, error) {
|
|
p := &plugin{
|
|
Config: config,
|
|
}
|
|
plugins, err := p.initPlugin(h)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return plugins, nil
|
|
}
|
|
|
|
// GetPlugins returns a list of all plugins.
|
|
func (h *PluginHost) GetPlugins(typ string) (list []any) {
|
|
for _, p := range h.plugins[typ] {
|
|
list = append(list, p.plugin)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (p *plugin) initPlugin(host *PluginHost) (plugins map[string]any, err error) {
|
|
const errText = "initPlugin: %w"
|
|
|
|
plugins = map[string]any{}
|
|
|
|
var filesystem fs.FS = nil
|
|
if strings.HasPrefix(p.Config.Path, embeddedPrefix) {
|
|
filesystem = host.embeddedPlugins
|
|
}
|
|
|
|
interpreter := interp.New(interp.Options{
|
|
GoPath: strings.TrimPrefix(p.Config.Path, embeddedPrefix),
|
|
SourcecodeFilesystem: filesystem,
|
|
Unrestricted: true,
|
|
})
|
|
|
|
if err := interpreter.Use(stdlib.Symbols); err != nil {
|
|
return nil, fmt.Errorf(errText, err)
|
|
}
|
|
|
|
if err := interpreter.Use(host.symbols); err != nil {
|
|
return nil, fmt.Errorf(errText, err)
|
|
}
|
|
|
|
if _, err := interpreter.Eval(fmt.Sprintf(`import "%s"`, p.Config.ImportPath)); err != nil {
|
|
return nil, fmt.Errorf(errText, err)
|
|
}
|
|
|
|
v, err := interpreter.Eval(filepath.Base(p.Config.ImportPath) + ".GetPlugin")
|
|
if err != nil {
|
|
return nil, fmt.Errorf(errText, err)
|
|
}
|
|
|
|
resultArray := v.Call([]reflect.Value{})
|
|
for _, result := range resultArray {
|
|
newPlugin := &plugin{
|
|
Config: p.Config,
|
|
plugin: result.Interface(),
|
|
}
|
|
for name, reflectType := range host.pluginTypes {
|
|
if result.Type().Implements(reflectType) {
|
|
host.plugins[name] = append(host.plugins[name], newPlugin)
|
|
plugins[name] = newPlugin.plugin
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|