slingshot

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 3, 2022 License: Apache-2.0 Imports: 5 Imported by: 0

README

========================
Slingshot Plugin Manager
========================

.. image:: https://img.shields.io/github/tag/klmitch/slingshot.svg
    :target: https://github.com/klmitch/slingshot/tags
.. image:: https://img.shields.io/hexpm/l/plug.svg
    :target: https://github.com/klmitch/slingshot/blob/master/LICENSE
.. image:: https://travis-ci.org/klmitch/slingshot.svg?branch=master
    :target: https://travis-ci.org/klmitch/slingshot
.. image:: https://coveralls.io/repos/github/klmitch/slingshot/badge.svg?branch=master
    :target: https://coveralls.io/github/klmitch/slingshot?branch=master
.. image:: https://godoc.org/github.com/klmitch/slingshot?status.svg
    :target: http://godoc.org/github.com/klmitch/slingshot
.. image:: https://img.shields.io/github/issues/klmitch/slingshot.svg
    :target: https://github.com/klmitch/slingshot/issues
.. image:: https://img.shields.io/github/issues-pr/klmitch/slingshot.svg
    :target: https://github.com/klmitch/slingshot/pulls
.. image:: https://goreportcard.com/badge/github.com/klmitch/slingshot
    :target: https://goreportcard.com/report/github.com/klmitch/slingshot

Package slingshot contains a plugin manager built on top of the
standard plugin package.  Plugins are loaded using the Load function,
which loads the specified plugin and invokes its SlingshotInit
function; this function will be passed a Slingshot object and any
parameters passed to the Load function, and is expected to use the
Register method of the Slingshot object to register one or more
plugins for use by the application (or the application's libraries).

Documentation

Overview

Package slingshot contains a plugin manager built on top of the standard plugin package. Plugins are loaded using the Load function, which takes the filesystem path to the compiled plugin and a map of parameters. The Load function uses the plugin package to load the plugin and invokes its SlingshotInit function, which must be declared to take as arguments a Slingshot interface object and the parameters (declared as a map from string to generic interface). The SlingshotInit function is then expected to use the Slingshot object's Register method, passing it a namespace, a key, the plugin object itself, and zero or more plugin options.

The slingshot package divides plugins up into namespaces. The namespaces should be unique for the application, e.g., "github.com/klmitch/slingshot". If the application requires multiple namespaces for different types of plugins, those namespaces should have a common prefix, e.g., "github.com/klmitch/slingshot/hooks" and "github.com/klmitch/slingshot/drivers".

Plugins are further divided up by a "key"; this is again a simple string, and its use will depend on how the application uses the plugin. For instance, in driver-style plugins, the key identifies the driver, while in hook-style plugins, the key names the hook to be triggered.

Plugins come in many varieties. The simplest to understand is perhaps the driver pattern plugin, where a driver is specified by a name. The desired driver may then be retrieved by simply calling GetPlugin with the appropriate namespace and driver name; this retrieves only the first-registered plugin with the given name. Hook-pattern plugins, on the other hand, expect to have all plugins with the same key invoked whenever the hook is triggered; to implement this pattern, call GetAllPlugins with the appropriate namespace and hook name, then iterate over the returned list.

A more complex pattern is the extension pattern. In this pattern, again, all the plugins are invoked in order, but each plugin calls the next plugin, and has the opportunity to process its return value. To make this easier to accommodate, the slingshot package provides the IterPlugins utility function, which encapsulates the list of plugins in an object with a Next method returning the next plugin. With this, an extension function could be written like so:

func Extension(nextPlug *PluginIter, finalFunc func() error) {
    next := nextPlug.Next()
    if next == nil {
        return finalFunc()
    }

    return callExtension(next, nextPlug, finalFunc)
}

In this example, callExtension is assumed to be a function that calls the plugin function; more on that below.

Some applications may have built-in plugins. For instance, a driver-style plugin may provide a mock driver. Such plugins can be registered directly using the Register function, which has the same calling convention as the Register method of the Slingshot object passed to the plugin initialization function.

What is a plugin? The GetPlugin and GetAllPlugins functions ultimately return a PluginMeta object. This object contains more than just the plugin itself; it contains a large amount of metadata, such as the filesystem path to the plugin, the plugin's base filename, and the namespace and key of the plugin. Additional options can be passed to the Register function to set a name, a plugin version, a license description, or documentation. An integer APIVersion can also be specified, which could be used to allow an application to still function with plugins implementing an older version of the plugin's interface. Finally, any arbitrary metadata can be provided, which could be used by the application for additional complex filtering when iterating over the list of plugins.

The Plugin element of the PluginMeta object is the actual plugin passed to the Register function. This is declared as a generic interface, so this may be anything, from a simple string to a function to an object implementing an interface. The callExtension function described above could thus be something of the form:

func callExtension(plug *PluginMeta, nextPlug *PluginIter, finalFunc func() error) {
    plug.Plugin.(func(*PluginIter, func() error) error)(nextPlug, finalFunc)
}

Finally, the slingshot package contains full-featured mocks, built on the "github.com/stretchr/testify/mock" mocking package. The MockRegistry type allows mocking the slingshot plugin registry. This mock can be installed for the use of functions such as GetPlugin by using SetRegistry from a test function like so:

defer SetRegistry(SetRegistry(mockReg))

The MockSlingshot type can be passed to a plugin initialization function. Finally, there is a MockNamespace type, which has a Name element to set the return result of the Namespace method call.

For more information on using the mocks, check out the documentation for "github.com/stretchr/testify/mock".

Index

Constants

View Source
const SlingshotInit = "SlingshotInit"

SlingshotInit is the name of the plugin initialization function that will be looked up.

Variables

View Source
var (
	ErrIncompatInit = errors.New("Incompatible plugin initializer")
	ErrInitPanic    = errors.New("Plugin initializer paniced")
)

Errors that may be returned

Functions

func Load

func Load(path string, params map[string]interface{}) error

Load loads a plugin and instructs it to register its plugin points with the Slingshot registry.

func Register

func Register(namespace, key string, plugin interface{}, opts ...PluginOption)

Register is for registering a "core" plugin--that is, a plugin that is implemented within the code of the application, rather than one loaded from an external file using the plugin package.

Types

type MockNamespace

type MockNamespace struct {
	mock.Mock
	Name string
}

MockNamespace is a mock object for Namespace.

func (*MockNamespace) Add

func (ns *MockNamespace) Add(key string, plugin *PluginMeta)

Add adds a new plugin descriptor under the given key.

func (*MockNamespace) Get

func (ns *MockNamespace) Get(key string) (*PluginMeta, bool)

Get returns the first plugin descriptor for the given key. If there are no descriptors for that key, the second value will be false.

func (*MockNamespace) GetAll

func (ns *MockNamespace) GetAll(key string) ([]*PluginMeta, bool)

GetAll returns all the plugin descriptors for the given key. If there are no descriptors for that key, the second value will be false.

func (*MockNamespace) Namespace

func (ns *MockNamespace) Namespace() string

Namespace returns the namespace string. The Name element of the MockNamespace must be set for this method to work.

type MockRegistry

type MockRegistry struct {
	mock.Mock
}

MockRegistry is a mock object for Registry.

func (*MockRegistry) Get

func (reg *MockRegistry) Get(namespace string, create bool) (Namespace, bool)

Get gets a specified namespace from the registry. If the namespace doesn't have any entries and create is false, the second value will be false.

func (*MockRegistry) GetAllPlugins

func (reg *MockRegistry) GetAllPlugins(namespace, key string) ([]*PluginMeta, bool)

GetAllPlugins gets all the plugin descriptors for the designated namespace of the registry. If the namespace doesn't have any entries for the designated key, the second value will be false.

func (*MockRegistry) GetPlugin

func (reg *MockRegistry) GetPlugin(namespace, key string) (*PluginMeta, bool)

GetPlugin gets a specified plugin from the designated namespace of the registry. If the namespace doesn't have any entries for the designated key, the second value will be false.

func (*MockRegistry) Load

func (reg *MockRegistry) Load(path string, params map[string]interface{}) error

Load loads a plugin and instructs it to register its plugin points with the Slingshot registry.

func (*MockRegistry) Register

func (reg *MockRegistry) Register(namespace, key string, plugin interface{}, opts ...PluginOption)

Register is for registering a "core" plugin--that is, a plugin that is implemented within the code of the application, rather than one loaded from an external file using the plugin package.

type MockSlingshot

type MockSlingshot struct {
	mock.Mock
}

MockSlingshot is a mock object for Slingshot.

func (*MockSlingshot) Register

func (sling *MockSlingshot) Register(namespace, key string, plugin interface{}, opts ...PluginOption)

Register is for registering a "core" plugin--that is, a plugin that is implemented within the code of the application, rather than one loaded from an external file using the plugin package.

type Namespace

type Namespace interface {
	Namespace() string
	Get(key string) (*PluginMeta, bool)
	GetAll(key string) ([]*PluginMeta, bool)
	Add(key string, plugin *PluginMeta)
}

Namespace describes a Slingshot namespace.

func Get

func Get(namespace string, create bool) (Namespace, bool)

Get gets a specified namespace from the registry. If the namespace doesn't have any entries and create is false, the second value will be false.

type PluginIter

type PluginIter interface {
	Next() *PluginMeta
}

PluginIter describes a plugin iterator.

func IterPlugins

func IterPlugins(plugins []*PluginMeta) PluginIter

IterPlugins is a constructor for a PluginIter. Given a list of plugins, as returned by Namespace.GetAll, it returns an objects whose Next method will return each plugin in turn.

type PluginMeta

type PluginMeta struct {
	Path       string                 // Path to the plugin
	Filename   string                 // Basename of the plugin
	Namespace  string                 // Namespace the plugin exists in
	Key        string                 // The key the plugin belongs in
	Plugin     interface{}            // The actual plugin.
	Name       string                 // The name the plugin identifies as
	Version    string                 // The version of the plugin
	License    string                 // Text describing the plugin license
	Docs       string                 // Documentation for the plugin
	APIVersion int                    // The API version of the plugin
	Meta       map[string]interface{} // Additional metadata
}

PluginMeta contains the metadata for a registered plugin. This includes such things as the filename, the namespace (string), the key, the plugin version, the API version, or anything else the plugin may register.

func GetAllPlugins

func GetAllPlugins(namespace, key string) ([]*PluginMeta, bool)

GetAllPlugins gets all the plugin descriptors for the designated namespace of the registry. If the namespace doesn't have any entries for the designated key, the second value will be false.

func GetPlugin

func GetPlugin(namespace, key string) (*PluginMeta, bool)

GetPlugin gets a specified plugin from the designated namespace of the registry. If the namespace doesn't have any entries for the designated key, the second value will be false.

type PluginOption

type PluginOption func(meta *PluginMeta)

PluginOption is an option function that can be passed to the Plugin.Register method.

func APIVersion

func APIVersion(version int) PluginOption

APIVersion sets the version of the plugin API. This is a number that can be used by the application to determine how to call the plugin, if the plugin interface has been evolved.

func Docs

func Docs(docs string) PluginOption

Docs sets the documentation string for the plugin.

func License

func License(license string) PluginOption

License sets the license descriptor for the plugin.

func Meta

func Meta(key string, value interface{}) PluginOption

Meta allows setting any number of other pieces of metadata, which can be used by the application as desired.

func Name

func Name(name string) PluginOption

Name sets the name of the plugin, which may be distinct from the filename or key depending on the needs of the application.

func Version

func Version(version string) PluginOption

Version sets the version of the plugin.

type Registry

type Registry interface {
	Get(namespace string, create bool) (Namespace, bool)
	GetPlugin(namespace, key string) (*PluginMeta, bool)
	GetAllPlugins(namespace, key string) ([]*PluginMeta, bool)
	Register(namespace, key string, plugin interface{}, opts ...PluginOption)
	Load(path string, params map[string]interface{}) error
}

Registry describes the Slingshot registry.

func SetRegistry

func SetRegistry(newReg Registry) (oldReg Registry)

SetRegistry sets the registry used by the top-level functions to the specified value, returning the original value. This is intended for use by library callers to allow for mocking out the registry using the MockRegistry type.

type Slingshot

type Slingshot interface {
	Register(namespace, key string, plugin interface{}, opts ...PluginOption)
}

Slingshot is used to carry additional data through the plugin's initialization routine to the Register method.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL