plugin

package
v0.1.8 Latest Latest
Warning

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

Go to latest
Published: Apr 5, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package plugin provides a system for bundling UI components into cohesive packages.

Plugins allow developers to distribute collections of widgets, themes, and layouts as a single unit with proper lifecycle management and dependency tracking.

Overview

A plugin implements the Plugin interface and can register any combination of:

Creating a Plugin

Implement the Plugin interface:

type MyPlugin struct{}

func (p *MyPlugin) Name() string    { return "my-plugin" }
func (p *MyPlugin) Version() string { return "1.0.0" }

func (p *MyPlugin) Dependencies() []plugin.Dependency {
    return nil // No dependencies
}

func (p *MyPlugin) Init(ctx *plugin.PluginContext) error {
    // Register widgets
    ctx.Widgets.Register("my-button", NewMyButton)

    // Register themes
    ctx.Themes.Register("my-theme", myTheme)

    // Load assets
    ctx.Assets.LoadFont("my-font", fontData)

    return nil
}

func (p *MyPlugin) Shutdown() error {
    return nil // Cleanup if needed
}

Registering Plugins

Register plugins in your package's init() function:

func init() {
    plugin.Register(&MyPlugin{}, plugin.PluginInfo{
        Name:        "my-plugin",
        Description: "My custom UI plugin",
        Version:     "1.0.0",
        Author:      "My Team",
        License:     "MIT",
    })
}

Using Plugins

In your application, initialize all registered plugins:

func main() {
    // Initialize all plugins
    if err := plugin.Initialize(); err != nil {
        log.Fatal(err)
    }
    defer plugin.Shutdown()

    // List available plugins
    for _, name := range plugin.List() {
        info, _ := plugin.Info(name)
        fmt.Printf("%s v%s - %s\n", name, info.Version, info.Description)
    }

    // Use components from plugins
    widget, _ := registry.CreateWidget("my-button", nil)
    theme, _ := theme.Get("my-theme")
}

Dependencies

Plugins can declare dependencies on other plugins:

func (p *MyPlugin) Dependencies() []plugin.Dependency {
    return []plugin.Dependency{
        {Name: "base-plugin", Version: ">=1.0.0"},
    }
}

The PluginManager automatically resolves dependencies and initializes plugins in the correct order. Circular dependencies are detected and reported as errors.

Version Constraints

Version constraints support semantic versioning:

  • "1.0.0" - exact version
  • ">=1.0.0" - minimum version
  • "<=1.0.0" - maximum version
  • ">1.0.0" - greater than
  • "<1.0.0" - less than
  • ">=1.0.0,<2.0.0" - range (AND)

Thread Safety

All operations on the global PluginManager are thread-safe. Plugins are initialized sequentially in dependency order to avoid race conditions during registration.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrPluginNotFound is returned when a plugin is not registered.
	ErrPluginNotFound = errors.New("plugin not found")

	// ErrPluginExists is returned when attempting to register a duplicate plugin.
	ErrPluginExists = errors.New("plugin already registered")

	// ErrNilPlugin is returned when a nil plugin is provided.
	ErrNilPlugin = errors.New("plugin cannot be nil")

	// ErrEmptyName is returned when a plugin has an empty name.
	ErrEmptyName = errors.New("plugin name cannot be empty")

	// ErrAlreadyInitialized is returned when Initialize is called twice.
	ErrAlreadyInitialized = errors.New("plugins already initialized")

	// ErrNotInitialized is returned when operations require initialized plugins.
	ErrNotInitialized = errors.New("plugins not initialized")

	// ErrCircularDependency is returned when circular dependencies are detected.
	ErrCircularDependency = errors.New("circular dependency detected")

	// ErrDependencyNotFound is returned when a required dependency is not registered.
	ErrDependencyNotFound = errors.New("required dependency not found")

	// ErrVersionMismatch is returned when a dependency version constraint is not satisfied.
	ErrVersionMismatch = errors.New("version constraint not satisfied")
)

Common errors returned by plugin operations.

Functions

func ClearGlobalManager

func ClearGlobalManager() error

ClearGlobalManager removes all plugins from the global manager.

WARNING: This is primarily intended for testing. Use with caution in production code.

func Count

func Count() int

Count returns the number of registered plugins.

func Has

func Has(name string) bool

Has returns true if a plugin with the given name is registered.

func Initialize

func Initialize() error

Initialize loads all registered plugins from the global manager.

Call this at application startup, after all plugins have been registered (typically via init() functions):

func main() {
    if err := plugin.Initialize(); err != nil {
        log.Fatal(err)
    }
    defer plugin.Shutdown()

    // Application code...
}

func InitializeWithContext

func InitializeWithContext(ctx *PluginContext) error

InitializeWithContext loads plugins using a custom context.

func IsInitialized

func IsInitialized() bool

IsInitialized returns true if the global manager has been initialized.

func List

func List() []string

List returns the names of all registered plugins.

func LoadOrder

func LoadOrder() []string

LoadOrder returns the initialization order of plugins.

func MustRegister

func MustRegister(plugin Plugin, info ...PluginInfo)

MustRegister is like Register but panics if registration fails.

Use this in init() functions where registration failure indicates a programming error.

func Register

func Register(plugin Plugin, info ...PluginInfo) error

Register adds a plugin to the global manager.

This is typically called from init() functions:

func init() {
    plugin.Register(&MyPlugin{}, plugin.PluginInfo{
        Name:        "my-plugin",
        Description: "My custom plugin",
    })
}

func Shutdown

func Shutdown() error

Shutdown unloads all plugins from the global manager.

Call this at application shutdown to allow plugins to release resources.

Types

type AssetLoader

type AssetLoader interface {
	// LoadFont registers a font with the given name.
	//
	// The data parameter should contain the raw font file data
	// (typically TTF or OTF format). The name is used to reference
	// the font in typography settings.
	//
	// Returns an error if the font data is invalid or loading fails.
	LoadFont(name string, data []byte) error

	// LoadIcon registers an icon with the given name.
	//
	// The data parameter should contain the icon image data
	// (typically PNG or SVG format). The name is used to reference
	// the icon in widgets.
	//
	// Returns an error if the icon data is invalid or loading fails.
	LoadIcon(name string, data []byte) error

	// LoadImage registers an image with the given name.
	//
	// The data parameter should contain the image data
	// (typically PNG, JPEG, or WebP format). The name is used to
	// reference the image in widgets and themes.
	//
	// Returns an error if the image data is invalid or loading fails.
	LoadImage(name string, data []byte) error
}

AssetLoader provides methods for loading plugin resources.

Plugins use the AssetLoader to load fonts, icons, and images during initialization. The loaded assets are then available to widgets and themes throughout the application.

Implementations should be thread-safe as plugins may be initialized concurrently in some scenarios.

Example:

func (p *MyPlugin) Init(ctx *PluginContext) error {
    // Load a font
    if err := ctx.Assets.LoadFont("roboto", robotoData); err != nil {
        return fmt.Errorf("failed to load roboto font: %w", err)
    }

    // Load icons
    if err := ctx.Assets.LoadIcon("add", addIconData); err != nil {
        return fmt.Errorf("failed to load add icon: %w", err)
    }

    // Load images
    if err := ctx.Assets.LoadImage("logo", logoData); err != nil {
        return fmt.Errorf("failed to load logo: %w", err)
    }

    return nil
}

type Dependency

type Dependency struct {
	// Name is the unique identifier of the required plugin.
	Name string

	// Version is a semantic version constraint.
	//
	// Supported formats:
	//   - "1.0.0" - exact version match
	//   - ">=1.0.0" - minimum version
	//   - "<=1.0.0" - maximum version
	//   - ">1.0.0" - greater than
	//   - "<1.0.0" - less than
	//   - ">=1.0.0,<2.0.0" - range (AND condition)
	//   - "" - any version
	Version string
}

Dependency declares a dependency on another plugin.

Plugins can depend on other plugins by name and version constraint. The plugin manager resolves dependencies and initializes plugins in the correct order.

Example:

func (p *MyPlugin) Dependencies() []Dependency {
    return []Dependency{
        {Name: "base-widgets", Version: ">=1.0.0"},
        {Name: "icons-pack", Version: ">=2.0.0,<3.0.0"},
    }
}

type MemoryAssetLoader

type MemoryAssetLoader struct {
	// contains filtered or unexported fields
}

MemoryAssetLoader is a simple in-memory implementation of AssetLoader.

It stores all loaded assets in memory and provides methods to retrieve them. This is useful for testing and simple applications.

MemoryAssetLoader is thread-safe.

func NewMemoryAssetLoader

func NewMemoryAssetLoader() *MemoryAssetLoader

NewMemoryAssetLoader creates a new MemoryAssetLoader.

func (*MemoryAssetLoader) Clear

func (m *MemoryAssetLoader) Clear()

Clear removes all loaded assets.

func (*MemoryAssetLoader) FontCount

func (m *MemoryAssetLoader) FontCount() int

FontCount returns the number of loaded fonts.

func (*MemoryAssetLoader) GetFont

func (m *MemoryAssetLoader) GetFont(name string) ([]byte, bool)

GetFont retrieves a loaded font by name.

Returns the font data and true if found, or nil and false if not found.

func (*MemoryAssetLoader) GetIcon

func (m *MemoryAssetLoader) GetIcon(name string) ([]byte, bool)

GetIcon retrieves a loaded icon by name.

Returns the icon data and true if found, or nil and false if not found.

func (*MemoryAssetLoader) GetImage

func (m *MemoryAssetLoader) GetImage(name string) ([]byte, bool)

GetImage retrieves a loaded image by name.

Returns the image data and true if found, or nil and false if not found.

func (*MemoryAssetLoader) IconCount

func (m *MemoryAssetLoader) IconCount() int

IconCount returns the number of loaded icons.

func (*MemoryAssetLoader) ImageCount

func (m *MemoryAssetLoader) ImageCount() int

ImageCount returns the number of loaded images.

func (*MemoryAssetLoader) LoadFont

func (m *MemoryAssetLoader) LoadFont(name string, data []byte) error

LoadFont implements AssetLoader.

func (*MemoryAssetLoader) LoadIcon

func (m *MemoryAssetLoader) LoadIcon(name string, data []byte) error

LoadIcon implements AssetLoader.

func (*MemoryAssetLoader) LoadImage

func (m *MemoryAssetLoader) LoadImage(name string, data []byte) error

LoadImage implements AssetLoader.

type Plugin

type Plugin interface {
	// Name returns the unique identifier for this plugin.
	//
	// Plugin names should be lowercase, hyphen-separated, and unique
	// within an application. Example: "material3", "corporate-design"
	Name() string

	// Version returns the semantic version of this plugin.
	//
	// Version strings should follow semantic versioning (semver):
	// "MAJOR.MINOR.PATCH", e.g., "1.0.0", "2.1.3"
	Version() string

	// Dependencies returns the list of plugins this plugin depends on.
	//
	// The plugin manager will ensure all dependencies are initialized
	// before this plugin's Init method is called. Return nil or an
	// empty slice if the plugin has no dependencies.
	Dependencies() []Dependency

	// Init is called when the plugin is initialized.
	//
	// Use the provided [PluginContext] to register widgets, themes,
	// layouts, and load assets. The context provides thread-safe
	// access to all registries.
	//
	// Return an error if initialization fails. This will prevent
	// the application from starting.
	Init(ctx *PluginContext) error

	// Shutdown is called when the plugin is unloaded.
	//
	// Use this method to release any resources allocated during Init.
	// Plugins are shut down in reverse initialization order.
	//
	// Return an error if shutdown fails. All plugins will still be
	// attempted to shut down even if one fails.
	Shutdown() error
}

Plugin provides bundled UI components with lifecycle management.

A plugin is a cohesive unit that can register widgets, themes, layouts, and assets. Plugins support dependency declaration, allowing the plugin system to initialize them in the correct order.

Implement this interface to create a custom plugin:

type MyPlugin struct{}

func (p *MyPlugin) Name() string    { return "my-plugin" }
func (p *MyPlugin) Version() string { return "1.0.0" }

func (p *MyPlugin) Dependencies() []Dependency {
    return nil
}

func (p *MyPlugin) Init(ctx *PluginContext) error {
    ctx.Widgets.Register("my-button", NewMyButton)
    return nil
}

func (p *MyPlugin) Shutdown() error {
    return nil
}

func Get

func Get(name string) (Plugin, bool)

Get returns a plugin by name from the global manager.

type PluginContext

type PluginContext struct {
	// Widgets provides access to the widget registry.
	//
	// Use this to register widget factories that create instances of
	// your custom widgets.
	Widgets *registry.WidgetRegistry

	// Themes provides access to the theme registry.
	//
	// Use this to register themes that define colors, typography,
	// spacing, and other visual properties.
	Themes *theme.ThemeRegistry

	// Layouts provides access to the layout registry.
	//
	// Use this to register custom layout algorithms that control
	// how widgets are arranged.
	Layouts *layout.Registry

	// Assets provides methods for loading plugin resources.
	//
	// Use this to load fonts, icons, and images that your plugin
	// provides.
	Assets AssetLoader
}

PluginContext provides access to UI registries for plugin initialization.

When a plugin's Init method is called, it receives a PluginContext that provides access to the widget, theme, and layout registries, as well as an asset loader for fonts, icons, and images.

All registry operations are thread-safe, but plugins should still be careful about the order of registration to avoid dependencies on components that haven't been registered yet.

Example:

func (p *MyPlugin) Init(ctx *PluginContext) error {
    // Register widgets
    ctx.Widgets.Register("my-button", NewMyButton, registry.WidgetInfo{
        Name:        "my-button",
        Description: "A custom button widget",
        Category:    registry.CategoryInput,
    })

    // Register themes
    ctx.Themes.Register("my-theme", myTheme, theme.ThemeInfo{
        Name:        "My Theme",
        Description: "A custom theme",
    })

    // Register layouts
    ctx.Layouts.Register(&MyLayout{})

    // Load assets
    ctx.Assets.LoadFont("my-font", fontData)
    ctx.Assets.LoadIcon("my-icon", iconData)

    return nil
}

func NewDefaultPluginContext

func NewDefaultPluginContext() *PluginContext

NewDefaultPluginContext creates a PluginContext with global registries.

This is the standard context used when initializing plugins through the global plugin manager.

func NewPluginContext

func NewPluginContext(
	widgets *registry.WidgetRegistry,
	themes *theme.ThemeRegistry,
	layouts *layout.Registry,
	assets AssetLoader,
) *PluginContext

NewPluginContext creates a new PluginContext with the given registries.

If any registry is nil, the global registry for that type is used. If assets is nil, a no-op asset loader is used.

type PluginInfo

type PluginInfo struct {
	// Name is the unique identifier for the plugin.
	// Should match Plugin.Name().
	Name string

	// Description is a brief description of what the plugin provides.
	Description string

	// Version is the semantic version of the plugin.
	// Should match Plugin.Version().
	Version string

	// Author is the creator or maintainer of the plugin.
	Author string

	// License is the license under which the plugin is distributed.
	// Example: "MIT", "Apache-2.0", "Proprietary"
	License string

	// Homepage is a URL to the plugin's homepage or repository.
	Homepage string

	// Dependencies lists the plugins this plugin depends on.
	// This is populated from Plugin.Dependencies() during registration.
	Dependencies []Dependency
}

PluginInfo describes metadata about a registered plugin.

PluginInfo provides human-readable information about a plugin, including its name, description, author, and license. This metadata can be used to build plugin management UIs or generate documentation.

Example:

info := PluginInfo{
    Name:        "material3",
    Description: "Google Material Design 3 components",
    Version:     "1.0.0",
    Author:      "gogpu team",
    License:     "MIT",
    Homepage:    "https://github.com/gogpu/ui-material3",
}

func AllInfo

func AllInfo() []PluginInfo

AllInfo returns information about all registered plugins.

func Info

func Info(name string) (PluginInfo, bool)

Info returns information about a registered plugin.

type PluginManager

type PluginManager struct {
	// contains filtered or unexported fields
}

PluginManager manages plugin registration, initialization, and shutdown.

The plugin manager maintains a registry of plugins and handles their lifecycle. It resolves dependencies between plugins and initializes them in the correct order.

For most use cases, use the package-level functions (Register, Initialize, Shutdown, List, Info) which operate on a global plugin manager.

Create a custom PluginManager for testing or isolated use cases.

func GlobalManager

func GlobalManager() *PluginManager

GlobalManager returns the global plugin manager.

This is useful for advanced use cases where direct access to the manager is needed.

func NewPluginManager

func NewPluginManager() *PluginManager

NewPluginManager creates a new PluginManager.

The manager starts empty and uninitialized. Register plugins with PluginManager.Register and then call PluginManager.Initialize.

func (*PluginManager) AllInfo

func (m *PluginManager) AllInfo() []PluginInfo

AllInfo returns information about all registered plugins.

The returned slice is sorted by plugin name.

func (*PluginManager) Clear

func (m *PluginManager) Clear() error

Clear removes all registered plugins.

If plugins are initialized, they are shut down first. This is primarily useful for testing.

func (*PluginManager) Count

func (m *PluginManager) Count() int

Count returns the number of registered plugins.

func (*PluginManager) Get

func (m *PluginManager) Get(name string) (Plugin, bool)

Get returns a plugin by name.

Returns false as the second value if the plugin is not registered.

func (*PluginManager) Has

func (m *PluginManager) Has(name string) bool

Has returns true if a plugin with the given name is registered.

func (*PluginManager) Info

func (m *PluginManager) Info(name string) (PluginInfo, bool)

Info returns information about a registered plugin.

Returns false as the second value if the plugin is not registered.

func (*PluginManager) Initialize

func (m *PluginManager) Initialize() error

Initialize loads all registered plugins in dependency order.

This method: 1. Validates all dependencies are registered 2. Checks version constraints 3. Resolves initialization order via topological sort 4. Calls Init() on each plugin in order

Returns an error if: - Already initialized (ErrAlreadyInitialized) - A dependency is not registered (ErrDependencyNotFound) - A version constraint is not satisfied (ErrVersionMismatch) - Circular dependencies are detected (ErrCircularDependency) - A plugin's Init() method returns an error

func (*PluginManager) InitializeWithContext

func (m *PluginManager) InitializeWithContext(ctx *PluginContext) error

InitializeWithContext loads all plugins using a custom context.

This allows using custom registries or asset loaders instead of the global ones.

func (*PluginManager) IsInitialized

func (m *PluginManager) IsInitialized() bool

IsInitialized returns true if plugins have been initialized.

func (*PluginManager) List

func (m *PluginManager) List() []string

List returns the names of all registered plugins in sorted order.

func (*PluginManager) LoadOrder

func (m *PluginManager) LoadOrder() []string

LoadOrder returns the order in which plugins were initialized.

Returns nil if plugins have not been initialized.

func (*PluginManager) MustRegister

func (m *PluginManager) MustRegister(plugin Plugin, info ...PluginInfo)

MustRegister is like Register but panics if registration fails.

This is intended for use in init() functions where registration failure indicates a programming error.

func (*PluginManager) Register

func (m *PluginManager) Register(plugin Plugin, info ...PluginInfo) error

Register adds a plugin to the manager.

The plugin's Name() method is used as the identifier. If info is provided, it stores metadata about the plugin for discovery purposes.

Returns ErrNilPlugin if plugin is nil, ErrEmptyName if the plugin's name is empty, or ErrPluginExists if a plugin with the same name is already registered.

Note: Registration does not initialize the plugin. Call Initialize after all plugins are registered.

func (*PluginManager) Shutdown

func (m *PluginManager) Shutdown() error

Shutdown unloads all plugins in reverse initialization order.

This method calls Shutdown() on each plugin, starting with those that were initialized last (to respect dependency order).

Returns an error if plugins were not initialized, or if any plugin's Shutdown() method returns an error. All plugins are attempted to shut down even if some fail.

Jump to

Keyboard shortcuts

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