///|
/// Order selected members so that a member never publishes before a selected
/// member it imports. Stable: ties keep the moon.work member order.
pub fn topo_sort(
selected : Array[Member],
items : Array[Member],
) -> Array[Member] {
// Member name → member, for import resolution.
let by_name : Map[String, Member] = Map([])
for item in items {
by_name[item.name] = item
}
let selected_names : Map[String, Unit] = Map([])
for item in selected {
selected_names[item.name] = ()
}
// Kahn's algorithm over the selected subgraph.
let deps : Map[String, Int] = Map([])
let dependents : Map[String, Array[String]] = Map([])
for item in selected {
deps[item.name] = 0
dependents[item.name] = []
}
for item in selected {
for imported in item.imports {
match by_name.get(imported) {
Some(target) =>
if selected_names.contains(target.name) {
// item imports target: target publishes first.
let current = match deps.get(item.name) {
Some(count) => count
None => 0
}
deps[item.name] = current + 1
dependents[target.name].push(item.name)
}
None => ()
}
}
}
let order : Array[Member] = []
let ready : Array[String] = []
for item in selected {
let count = match deps.get(item.name) {
Some(count) => count
None => 0
}
if count == 0 {
ready.push(item.name)
}
}
while ready.length() > 0 {
let name = ready.remove(0)
for item in selected {
if item.name == name {
order.push(item)
break
}
}
match dependents.get(name) {
Some(list) =>
for dependent in list {
let rest = match deps.get(dependent) {
Some(count) => count - 1
None => 0
}
deps[dependent] = rest
if rest == 0 {
ready.push(dependent)
}
}
None => ()
}
}
// Cycles cannot happen between real modules; append leftovers defensively.
for item in selected {
let mut found = false
for done in order {
if done.name == item.name {
found = true
break
}
}
if !found {
order.push(item)
}
}
order
}