core/plugin/topo_sort.go
Alex Dunmow 7c20538a4e feat: WO-PS-009 SDK plugin types
- plugin/types.go: Pool, EmailSender, JobHandlerFunc, Dependency,
  AdminPage, AIAction, MasterPageDefinition, status/source constants,
  MediaAnalyzedEvent, ModerationDecisionEvent, MediaHooksProvider,
  DirectoryExtensions
- plugin/deps.go: ServiceDeps with typed capability interfaces
  (Content, Settings, Gating, Crypto, ToolRegistry, JobRunner,
  EmbeddingService, RAGService)
- plugin/registration.go: PluginRegistration, RegisterFunc
- plugin/service.go: ConnectServiceBinding with generics, ServiceMount,
  ServiceRegistration
- plugin/provisioner.go: Provisioner interface with all config types
- plugin/css_manifest.go: CSSManifest, MergedCSSManifest, MergeCSSManifests
- plugin/version.go: ParseModVersion, CompareVersions
- plugin/topo_sort.go: TopologicalSort (Kahn's algorithm)
- plugin/block_registry.go: PluginBlockRegistry (auto-prefixing wrapper)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-30 22:41:52 +08:00

65 lines
1.5 KiB
Go

package plugin
import "fmt"
// TopologicalSort orders plugins so that dependencies come before dependents.
// Returns error on circular dependencies or missing required dependencies.
func TopologicalSort(plugins []PluginRegistration) ([]PluginRegistration, error) {
byName := make(map[string]*PluginRegistration, len(plugins))
for i := range plugins {
byName[plugins[i].Name] = &plugins[i]
}
for _, p := range plugins {
for _, dep := range p.Dependencies {
if dep.Required {
if _, ok := byName[dep.Plugin]; !ok {
return nil, fmt.Errorf("plugin %q requires %q, but it is not available", p.Name, dep.Plugin)
}
}
}
}
inDegree := make(map[string]int, len(plugins))
dependents := make(map[string][]string)
for _, p := range plugins {
if _, ok := inDegree[p.Name]; !ok {
inDegree[p.Name] = 0
}
for _, dep := range p.Dependencies {
if _, ok := byName[dep.Plugin]; ok {
inDegree[p.Name]++
dependents[dep.Plugin] = append(dependents[dep.Plugin], p.Name)
}
}
}
var queue []string
for _, p := range plugins {
if inDegree[p.Name] == 0 {
queue = append(queue, p.Name)
}
}
var result []PluginRegistration
for len(queue) > 0 {
name := queue[0]
queue = queue[1:]
result = append(result, *byName[name])
for _, dep := range dependents[name] {
inDegree[dep]--
if inDegree[dep] == 0 {
queue = append(queue, dep)
}
}
}
if len(result) != len(plugins) {
return nil, fmt.Errorf("circular dependency detected among plugins")
}
return result, nil
}