Browse Source

Add godoc to plugin subpackage

master
Tyler Sommer 4 months ago
parent
commit
d2f6a613d7
Signed by: tyler-sommer GPG Key ID: C09C010500DBD008
1 changed files with 53 additions and 12 deletions
  1. +53
    -12
      plugin/plugin.go

+ 53
- 12
plugin/plugin.go View File

@ -8,57 +8,80 @@ import (
"github.com/pkg/errors"
)
// A Plugin is the basic interface that all squircy3 plugins must implement.
type Plugin interface {
// Name returns the unique name for the Plugin.
Name() string
}
type InitHandler interface {
// PluginInitHandler is implemented by types that want to be notified when
// another plugin is initialized.
type PluginInitHandler interface {
// HandlePluginInit is called after each other Plugin is initialized.
// Plugin initialization occurs when the Configure method is called on
// a plugin.Manager.
HandlePluginInit(Plugin)
}
// ShutdownHandler is implemented by types that want to perform some action
// when the application is shutting down.
type ShutdownHandler interface {
// HandleShutdown is called when the application begins shutting down.
// All ShutdownHandlers are invoked concurrently, but each has a limited
// amount of time to gracefully clean up before the application forcefully
// exits.
HandleShutdown()
}
// An Initializer is a type that can create a Plugin implementation.
type Initializer interface {
// Initialize creates the corresponding Plugin for this Initializer.
Initialize(*Manager) (Plugin, error)
}
// An InitializerFunc is a function that implements Initializer.
type InitializerFunc func(*Manager) (Plugin, error)
func (f InitializerFunc) Initialize(m *Manager) (Plugin, error) {
return f(m)
}
// InitializeFromFile returns an Initializer that will attempt to load the
// specified plugin shared library from the filesystem.
// Shared library plugins must be built as a `main` package and must have
// an "Initialize" function defined at the package level. The "Initialize"
// function must be compatible with the Initialize method on the Initializer
// interface. That is, it must have the signature:
// func Initialize(*Manager) (Plugin, error)
func InitializeFromFile(p string) Initializer {
return InitializerFunc(func(m *Manager) (Plugin, error) {
pl, err := plugin.Open(p)
if err != nil {
return nil, errors.Wrapf(err, "unable to open plugin (%s)", p)
return nil, errors.Wrapf(err, "unable (%s) to open plugin", p)
}
in, err := pl.Lookup("Initialize")
if err != nil {
return nil, errors.Wrapf(err, "plugin does not export Initialize (%s)", p)
return nil, errors.Wrapf(err, "plugin (%s) does not export Initialize", p)
}
fn, ok := in.(func(*Manager) (Plugin, error))
if !ok {
err := errors.Errorf("plugin has invalid type for Initialize (%s): expected func(*plugin.Manager) (plugin.Plugin, error)", p)
return nil, err
return nil, errors.Errorf("plugin (%s) has invalid type for Initialize: expected func(*plugin.Manager) (plugin.Plugin, error), got %T", p, in)
}
plg, err := fn(m)
if err != nil {
return nil, errors.Wrapf(err, "plugin init failed (%s)", p)
return nil, errors.Wrapf(err, "plugin (%s) init failed", p)
}
return plg, nil
})
}
// A Manager controls the loading and configuration of plugins.
type Manager struct {
plugins []Initializer
loaded map[string]Plugin
onInit []InitHandler
onInit []PluginInitHandler
onShutdown []ShutdownHandler
mu sync.RWMutex
@ -75,18 +98,25 @@ func NewManager(plugins ...string) *Manager {
}
}
func (m *Manager) OnPluginInit(h InitHandler) {
// OnPluginInit adds the given PluginInitHandler to be called when a plugin
// is initialized.
func (m *Manager) OnPluginInit(h PluginInitHandler) {
m.mu.Lock()
defer m.mu.Unlock()
m.onInit = append(m.onInit, h)
}
// OnShutdown adds the given ShutdownHandler to be called when the appliation
// is shutting down.
func (m *Manager) OnShutdown(h ShutdownHandler) {
m.mu.Lock()
defer m.mu.Unlock()
m.onShutdown = append(m.onShutdown, h)
}
// Shutdown begins the shut down process.
// Each ShutdownHandler is called concurrently and this method returns after
// all handlers have completed.
func (m *Manager) Shutdown() {
m.mu.RLock()
hs := make([]ShutdownHandler, len(m.onShutdown))
@ -103,6 +133,7 @@ func (m *Manager) Shutdown() {
wg.Wait()
}
// Loaded returns a list of plugins currently loaded.
func (m *Manager) Loaded() []string {
m.mu.RLock()
defer m.mu.RUnlock()
@ -113,6 +144,7 @@ func (m *Manager) Loaded() []string {
return ns
}
// Lookup returns the given plugin by name, or an error if it isn't loaded.
func (m *Manager) Lookup(name string) (Plugin, error) {
m.mu.RLock()
defer m.mu.RUnlock()
@ -122,16 +154,22 @@ func (m *Manager) Lookup(name string) (Plugin, error) {
return nil, errors.Errorf("no plugin named %s", name)
}
// Register adds a plugin Initializer to the Manager.
// Invoke all the registered Initializers by calling Configure on the Manager.
func (m *Manager) Register(initfn Initializer) {
m.mu.Lock()
defer m.mu.Unlock()
m.plugins = append(m.plugins, initfn)
}
// RegisterFunc is a shorthand method to register an InitializerFunc without
// extra type assertions.
func (m *Manager) RegisterFunc(initfn func(m *Manager) (Plugin, error)) {
m.Register(InitializerFunc(initfn))
}
// Configure attempts to load and configure all registered plugins.
// An error will be returned for each failed initialization.
func (m *Manager) Configure() []error {
var errs []error
m.mu.Lock()
@ -148,13 +186,13 @@ func (m *Manager) Configure() []error {
// get a fresh copy of init handlers before each init;
// plugins may add handlers in this loop and those should be accounted
// for on subsequent inits.
inits := append([]InitHandler{}, m.onInit...)
inits := append([]PluginInitHandler{}, m.onInit...)
m.mu.RUnlock()
// Manager should be unlocked while the plugin initializes; the plugin
// is free to use the Manager itself during init.
plg, err := p.Initialize(m)
if err != nil {
errs = append(errs, errors.Wrap(err, "plugin init failed"))
errs = append(errs, errors.Wrapf(err, "plugin (unknown) init failed"))
continue
}
pn := plg.Name()
@ -163,7 +201,7 @@ func (m *Manager) Configure() []error {
if !ok {
// not already loaded, add it
m.loaded[pn] = plg
if ih, ok := plg.(InitHandler); ok {
if ih, ok := plg.(PluginInitHandler); ok {
m.onInit = append(m.onInit, ih)
}
if sh, ok := plg.(ShutdownHandler); ok {
@ -174,7 +212,7 @@ func (m *Manager) Configure() []error {
m.mu.Unlock()
if ok {
// plugin was already loaded
errs = append(errs, errors.Errorf("plugin already loaded %s", pn))
errs = append(errs, errors.Errorf("plugin (%s) already loaded", pn))
continue
}
// run other plugin init handlers
@ -185,6 +223,9 @@ func (m *Manager) Configure() []error {
return errs
}
// Main is a helper function that can be used to provide a consistent main
// func. Plugins aren't normally executed anyway, but Go requires that every
// "main" package have a "main" func.
func Main(pluginName string) {
fmt.Println(pluginName, "- a plugin for squircy3")
}

Loading…
Cancel
Save