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 }