iop/packages/go/agenttask/dependency.go

85 lines
2.4 KiB
Go

package agenttask
import (
"regexp"
"sort"
"strings"
)
type dependencyStatus string
const (
dependencyReady dependencyStatus = "ready"
dependencyWaiting dependencyStatus = "waiting"
dependencyMissing dependencyStatus = "missing"
dependencyAmbiguous dependencyStatus = "ambiguous"
dependencyBlocked dependencyStatus = "blocked"
)
type dependencyResult struct {
Status dependencyStatus
Ref string
}
var dependencySeparator = regexp.MustCompile(`[^a-z0-9]+`)
func normalizeDependencyRef(value string) string {
value = strings.ToLower(strings.TrimSpace(value))
value = strings.Trim(value, "`'\"")
value = dependencySeparator.ReplaceAllString(value, "-")
return strings.Trim(value, "-")
}
// evaluateDependencies intentionally considers only ExplicitPredecessors.
// Directory ordinals and declared/unknown/overlapping write sets never create
// a dependency.
func evaluateDependencies(
unit WorkUnit,
workflow ProjectWorkflowSnapshot,
works map[WorkUnitID]WorkRecord,
) dependencyResult {
if len(unit.ExplicitPredecessors) == 0 {
return dependencyResult{Status: dependencyReady}
}
for _, predecessor := range unit.ExplicitPredecessors {
ref := normalizeDependencyRef(predecessor.Ref)
matches := make([]WorkUnit, 0, 1)
for _, candidate := range workflow.Units {
if candidate.ID == unit.ID {
continue
}
if normalizeDependencyRef(string(candidate.ID)) == ref {
matches = append(matches, candidate)
continue
}
for _, alias := range candidate.Aliases {
if normalizeDependencyRef(alias) == ref {
matches = append(matches, candidate)
break
}
}
}
sort.Slice(matches, func(left, right int) bool {
return matches[left].ID < matches[right].ID
})
switch len(matches) {
case 0:
return dependencyResult{Status: dependencyMissing, Ref: predecessor.Ref}
case 1:
default:
return dependencyResult{Status: dependencyAmbiguous, Ref: predecessor.Ref}
}
match := matches[0]
record, tracked := works[match.ID]
if match.Completed || tracked && record.State == WorkStateCompleted {
continue
}
if tracked && (record.State == WorkStateBlocked ||
record.State == WorkStateTerminalDeferred ||
record.State == WorkStateStopped) {
return dependencyResult{Status: dependencyBlocked, Ref: predecessor.Ref}
}
return dependencyResult{Status: dependencyWaiting, Ref: predecessor.Ref}
}
return dependencyResult{Status: dependencyReady}
}