Add shared bounded pipeline plans
This commit is contained in:
@@ -2,13 +2,134 @@ package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/stage"
|
||||
)
|
||||
|
||||
// BoundedPlan is one validated inclusive range of the canonical pipeline.
|
||||
// It owns effective endpoints and membership so command, runner, and
|
||||
// composition callers do not independently interpret range bounds.
|
||||
type BoundedPlan struct {
|
||||
stages []stage.Stage
|
||||
canonicalNames []string
|
||||
startIndex int
|
||||
endIndex int
|
||||
explicitFrom bool
|
||||
explicitThrough bool
|
||||
}
|
||||
|
||||
// BuildBoundedPlan selects an inclusive contiguous range of the canonical
|
||||
// pipeline. Empty endpoints default to the beginning or end respectively.
|
||||
func BuildBoundedPlan(from, through string) (BoundedPlan, error) {
|
||||
registry := stage.All()
|
||||
names := make([]string, len(registry))
|
||||
indices := make(map[string]int, len(registry))
|
||||
for index, candidate := range registry {
|
||||
if candidate == nil {
|
||||
return BoundedPlan{}, fmt.Errorf("build bounded plan: canonical stage %d is nil", index)
|
||||
}
|
||||
name := candidate.Name()
|
||||
if _, duplicate := indices[name]; duplicate {
|
||||
return BoundedPlan{}, fmt.Errorf("build bounded plan: duplicate canonical stage %q", name)
|
||||
}
|
||||
names[index] = name
|
||||
indices[name] = index
|
||||
}
|
||||
if len(registry) == 0 {
|
||||
return BoundedPlan{}, fmt.Errorf("build bounded plan: canonical stage registry is empty")
|
||||
}
|
||||
|
||||
start := 0
|
||||
if from != "" {
|
||||
var ok bool
|
||||
start, ok = indices[from]
|
||||
if !ok {
|
||||
return BoundedPlan{}, fmt.Errorf("build bounded plan: unknown from stage %q; valid stages: %s", from, strings.Join(names, ", "))
|
||||
}
|
||||
}
|
||||
end := len(registry) - 1
|
||||
if through != "" {
|
||||
var ok bool
|
||||
end, ok = indices[through]
|
||||
if !ok {
|
||||
return BoundedPlan{}, fmt.Errorf("build bounded plan: unknown through stage %q; valid stages: %s", through, strings.Join(names, ", "))
|
||||
}
|
||||
}
|
||||
if start > end {
|
||||
return BoundedPlan{}, fmt.Errorf("build bounded plan: from stage %q occurs after through stage %q; valid stages: %s", from, through, strings.Join(names, ", "))
|
||||
}
|
||||
|
||||
return BoundedPlan{
|
||||
stages: append([]stage.Stage(nil), registry[start:end+1]...),
|
||||
canonicalNames: append([]string(nil), names...),
|
||||
startIndex: start,
|
||||
endIndex: end,
|
||||
explicitFrom: from != "",
|
||||
explicitThrough: through != "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Stages returns a copy of the selected canonical stages.
|
||||
func (p BoundedPlan) Stages() []stage.Stage {
|
||||
return append([]stage.Stage(nil), p.stages...)
|
||||
}
|
||||
|
||||
// Names returns selected stage names in canonical order.
|
||||
func (p BoundedPlan) Names() []string {
|
||||
out := make([]string, 0, len(p.stages))
|
||||
for _, candidate := range p.stages {
|
||||
out = append(out, candidate.Name())
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// From returns the effective inclusive start stage.
|
||||
func (p BoundedPlan) From() string {
|
||||
if len(p.canonicalNames) == 0 || p.startIndex < 0 || p.startIndex >= len(p.canonicalNames) {
|
||||
return ""
|
||||
}
|
||||
return p.canonicalNames[p.startIndex]
|
||||
}
|
||||
|
||||
// Through returns the effective inclusive end stage.
|
||||
func (p BoundedPlan) Through() string {
|
||||
if len(p.canonicalNames) == 0 || p.endIndex < 0 || p.endIndex >= len(p.canonicalNames) {
|
||||
return ""
|
||||
}
|
||||
return p.canonicalNames[p.endIndex]
|
||||
}
|
||||
|
||||
// Contains reports whether a canonical stage is selected by the range.
|
||||
func (p BoundedPlan) Contains(name string) bool {
|
||||
for _, candidate := range p.stages {
|
||||
if candidate.Name() == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// PrefixNames returns canonical stages excluded before the selected start.
|
||||
func (p BoundedPlan) PrefixNames() []string {
|
||||
if p.startIndex <= 0 || p.startIndex > len(p.canonicalNames) {
|
||||
return nil
|
||||
}
|
||||
return append([]string(nil), p.canonicalNames[:p.startIndex]...)
|
||||
}
|
||||
|
||||
// HasExplicitBounds reports whether either endpoint was supplied by the caller.
|
||||
func (p BoundedPlan) HasExplicitBounds() bool {
|
||||
return p.explicitFrom || p.explicitThrough
|
||||
}
|
||||
|
||||
// BuildFullPlan returns the canonical full stage list in deterministic order.
|
||||
func BuildFullPlan() []stage.Stage {
|
||||
return stage.All()
|
||||
plan, err := BuildBoundedPlan("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return plan.Stages()
|
||||
}
|
||||
|
||||
// BuildSingleStagePlan returns a one-stage plan for an exact stage name.
|
||||
|
||||
Reference in New Issue
Block a user