refactor(10.2-01): nest framework packages under modules
- Move remaining beach packages and embedded admin assets\n- Rewrite framework, example, build, and gate paths
This commit is contained in:
220
modules/party/registry.go
Normal file
220
modules/party/registry.go
Normal file
@@ -0,0 +1,220 @@
|
||||
package party
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/backpack"
|
||||
"git.golem15.com/golem15/summercms/modules/pact"
|
||||
"git.golem15.com/golem15/summercms/modules/phrasebook"
|
||||
"git.golem15.com/golem15/summercms/modules/postcard"
|
||||
)
|
||||
|
||||
// Plugin is the required descriptor every compiled plugin implements.
|
||||
type Plugin interface {
|
||||
ID() string
|
||||
Requires() []string
|
||||
Register(app *backpack.App) error
|
||||
Boot(app *backpack.App) error
|
||||
}
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
plugins []Plugin
|
||||
)
|
||||
|
||||
// Register appends a plugin to the process-wide registry. Plugins call this
|
||||
// from init() after a blank import; Activate selects and orders them.
|
||||
func Register(p Plugin) {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
plugins = append(plugins, p)
|
||||
}
|
||||
|
||||
func snapshot() []Plugin {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
out := make([]Plugin, len(plugins))
|
||||
copy(out, plugins)
|
||||
return out
|
||||
}
|
||||
|
||||
// Activate resolves ids in manifest order, topologically sorts Requires(),
|
||||
// then runs every Register before any Boot.
|
||||
func Activate(app *backpack.App, ids []string) ([]Plugin, error) {
|
||||
if app == nil {
|
||||
return nil, fmt.Errorf("party: app is nil")
|
||||
}
|
||||
return activate(snapshot(), app, ids)
|
||||
}
|
||||
|
||||
func activate(registered []Plugin, app *backpack.App, ids []string) ([]Plugin, error) {
|
||||
byID := make(map[string]Plugin, len(registered))
|
||||
for _, p := range registered {
|
||||
if p == nil {
|
||||
continue
|
||||
}
|
||||
id := p.ID()
|
||||
if id == "" {
|
||||
return nil, fmt.Errorf("party: registered plugin has empty id")
|
||||
}
|
||||
if _, exists := byID[id]; exists {
|
||||
return nil, fmt.Errorf("party: duplicate plugin id %q", id)
|
||||
}
|
||||
byID[id] = p
|
||||
}
|
||||
|
||||
seen := make(map[string]struct{}, len(ids))
|
||||
selected := make([]Plugin, 0, len(ids))
|
||||
index := make(map[string]int, len(ids))
|
||||
for _, id := range ids {
|
||||
if id == "" {
|
||||
return nil, fmt.Errorf("party: empty plugin id in manifest")
|
||||
}
|
||||
if _, dup := seen[id]; dup {
|
||||
return nil, fmt.Errorf("party: duplicate plugin id %q", id)
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
p, ok := byID[id]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("party: plugin %q is not registered", id)
|
||||
}
|
||||
index[id] = len(selected)
|
||||
selected = append(selected, p)
|
||||
}
|
||||
|
||||
for _, p := range selected {
|
||||
for _, req := range p.Requires() {
|
||||
if _, ok := seen[req]; !ok {
|
||||
return nil, fmt.Errorf("party: plugin %q requires %q, which is not registered", p.ID(), req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ordered, err := topoSort(selected, index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderedIDs := make([]string, len(ordered))
|
||||
for i, p := range ordered {
|
||||
orderedIDs[i] = p.ID()
|
||||
}
|
||||
app.SetPlugins(orderedIDs)
|
||||
|
||||
for _, p := range ordered {
|
||||
if hc, ok := p.(pact.HasConfig); ok && app.Config != nil {
|
||||
if err := app.Config.MergePlugin(p.ID(), hc.ConfigFS()); err != nil {
|
||||
return nil, fmt.Errorf("party: config %s: %w", p.ID(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, p := range ordered {
|
||||
if err := p.Register(app); err != nil {
|
||||
return nil, fmt.Errorf("party: register %s: %w", p.ID(), err)
|
||||
}
|
||||
}
|
||||
if err := phrasebook.Activate(app, ordered); err != nil {
|
||||
return nil, fmt.Errorf("party: lang: %w", err)
|
||||
}
|
||||
if err := postcard.Activate(app, ordered); err != nil {
|
||||
return nil, fmt.Errorf("party: mail: %w", err)
|
||||
}
|
||||
for _, p := range ordered {
|
||||
if err := postcard.BootPlugin(app, p); err != nil {
|
||||
return nil, fmt.Errorf("party: boot %s: %w", p.ID(), err)
|
||||
}
|
||||
if err := p.Boot(app); err != nil {
|
||||
return nil, fmt.Errorf("party: boot %s: %w", p.ID(), err)
|
||||
}
|
||||
}
|
||||
return ordered, nil
|
||||
}
|
||||
|
||||
func topoSort(plugins []Plugin, index map[string]int) ([]Plugin, error) {
|
||||
byID := make(map[string]Plugin, len(plugins))
|
||||
indeg := make(map[string]int, len(plugins))
|
||||
dependents := make(map[string][]string, len(plugins))
|
||||
for _, p := range plugins {
|
||||
byID[p.ID()] = p
|
||||
indeg[p.ID()] = 0
|
||||
}
|
||||
for _, p := range plugins {
|
||||
seenReq := make(map[string]struct{})
|
||||
for _, req := range p.Requires() {
|
||||
if _, ok := seenReq[req]; ok {
|
||||
continue
|
||||
}
|
||||
seenReq[req] = struct{}{}
|
||||
if _, ok := byID[req]; !ok {
|
||||
continue
|
||||
}
|
||||
indeg[p.ID()]++
|
||||
dependents[req] = append(dependents[req], p.ID())
|
||||
}
|
||||
}
|
||||
|
||||
ready := make([]string, 0, len(plugins))
|
||||
for _, p := range plugins {
|
||||
if indeg[p.ID()] == 0 {
|
||||
ready = append(ready, p.ID())
|
||||
}
|
||||
}
|
||||
|
||||
out := make([]Plugin, 0, len(plugins))
|
||||
for len(ready) > 0 {
|
||||
id := ready[0]
|
||||
ready = ready[1:]
|
||||
out = append(out, byID[id])
|
||||
next := append([]string(nil), dependents[id]...)
|
||||
sortByIndex(next, index)
|
||||
for _, dep := range next {
|
||||
indeg[dep]--
|
||||
if indeg[dep] == 0 {
|
||||
ready = insertByIndex(ready, dep, index)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(out) != len(plugins) {
|
||||
leftover := make([]string, 0)
|
||||
done := make(map[string]struct{}, len(out))
|
||||
for _, p := range out {
|
||||
done[p.ID()] = struct{}{}
|
||||
}
|
||||
for _, p := range plugins {
|
||||
if _, ok := done[p.ID()]; !ok {
|
||||
leftover = append(leftover, p.ID())
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("party: dependency cycle involving %s", strings.Join(leftover, ", "))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func sortByIndex(ids []string, index map[string]int) {
|
||||
for i := 1; i < len(ids); i++ {
|
||||
j := i
|
||||
for j > 0 && index[ids[j]] < index[ids[j-1]] {
|
||||
ids[j], ids[j-1] = ids[j-1], ids[j]
|
||||
j--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func insertByIndex(ready []string, id string, index map[string]int) []string {
|
||||
at := len(ready)
|
||||
for i, existing := range ready {
|
||||
if index[id] < index[existing] {
|
||||
at = i
|
||||
break
|
||||
}
|
||||
}
|
||||
ready = append(ready, "")
|
||||
copy(ready[at+1:], ready[at:])
|
||||
ready[at] = id
|
||||
return ready
|
||||
}
|
||||
Reference in New Issue
Block a user