148 lines
4.5 KiB
Go
148 lines
4.5 KiB
Go
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
|
|
"gitea.maximumdirect.net/eric/narratio/internal/config"
|
|
)
|
|
|
|
type artifactSelectionFlag struct {
|
|
values []string
|
|
}
|
|
|
|
func (f *artifactSelectionFlag) String() string {
|
|
return strings.Join(f.values, ",")
|
|
}
|
|
|
|
func (f *artifactSelectionFlag) Set(value string) error {
|
|
f.values = append(f.values, value)
|
|
return nil
|
|
}
|
|
|
|
func (f *artifactSelectionFlag) Normalize() ([]string, error) {
|
|
if len(f.values) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
seen := map[string]struct{}{}
|
|
out := make([]string, 0, len(f.values))
|
|
for _, raw := range f.values {
|
|
for _, part := range strings.Split(raw, ",") {
|
|
name := strings.TrimSpace(part)
|
|
if name == "" {
|
|
return nil, fmt.Errorf("artifact names must be non-empty")
|
|
}
|
|
if _, ok := seen[name]; ok {
|
|
continue
|
|
}
|
|
seen[name] = struct{}{}
|
|
out = append(out, name)
|
|
}
|
|
}
|
|
|
|
sort.Strings(out)
|
|
return out, nil
|
|
}
|
|
|
|
func validateSelectedArtifacts(cfg *config.Config, selected []string) error {
|
|
_, err := resolveEffectiveArtifacts(cfg, selected)
|
|
return err
|
|
}
|
|
|
|
func resolveEffectiveArtifacts(cfg *config.Config, selected []string) (artifacts.EffectiveArtifactSet, error) {
|
|
if cfg == nil || cfg.Pipeline == nil || cfg.Pipeline.Scriptorium == nil {
|
|
if len(selected) == 0 {
|
|
return artifacts.ResolveEffectiveArtifactSet(nil, nil)
|
|
}
|
|
return artifacts.EffectiveArtifactSet{}, fmt.Errorf("--artifacts requires pipeline.scriptorium.artifacts to be configured")
|
|
}
|
|
configured := artifacts.ConfiguredArtifactDefinitions(cfg.Pipeline.Scriptorium.Artifacts)
|
|
normalized, err := normalizeArtifactSelection(cfg, selected)
|
|
if err != nil {
|
|
return artifacts.EffectiveArtifactSet{}, err
|
|
}
|
|
if len(normalized) > 0 && len(configured) == 0 {
|
|
return artifacts.EffectiveArtifactSet{}, fmt.Errorf("--artifacts requires at least one configured artifact in pipeline.scriptorium.artifacts")
|
|
}
|
|
effective, err := artifacts.ResolveEffectiveArtifactSet(configured, normalized)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "is not configured") {
|
|
return artifacts.EffectiveArtifactSet{}, fmt.Errorf("--artifacts includes unknown artifact %q", selectedArtifactName(err))
|
|
}
|
|
return artifacts.EffectiveArtifactSet{}, err
|
|
}
|
|
if err := validateEffectiveArtifactConfiguration(cfg.Pipeline.Scriptorium.Artifacts, effective); err != nil {
|
|
return artifacts.EffectiveArtifactSet{}, err
|
|
}
|
|
return effective.WithOrigins(effectiveArtifactOrigins(cfg.Pipeline)), nil
|
|
}
|
|
|
|
func normalizeArtifactSelection(cfg *config.Config, selected []string) ([]string, error) {
|
|
if len(selected) == 0 {
|
|
return nil, nil
|
|
}
|
|
configured := cfg.Pipeline.Scriptorium.Artifacts
|
|
families := config.ArtifactFamilies(cfg.Pipeline).Families
|
|
set := make(map[string]struct{}, len(selected))
|
|
for _, raw := range selected {
|
|
key := strings.TrimSpace(raw)
|
|
if key == "" {
|
|
return nil, fmt.Errorf("artifact names must be non-empty")
|
|
}
|
|
if family, ok := families[key]; ok {
|
|
for _, member := range family.Members {
|
|
set[member] = struct{}{}
|
|
}
|
|
continue
|
|
}
|
|
if _, ok := configured[key]; !ok {
|
|
return nil, fmt.Errorf("--artifacts includes unknown artifact %q", key)
|
|
}
|
|
set[key] = struct{}{}
|
|
}
|
|
normalized := make([]string, 0, len(set))
|
|
for key := range set {
|
|
normalized = append(normalized, key)
|
|
}
|
|
sort.Strings(normalized)
|
|
return normalized, nil
|
|
}
|
|
|
|
func effectiveArtifactOrigins(pipeline *config.PipelineConfig) map[string]artifacts.EffectiveArtifactOrigin {
|
|
catalog := config.ArtifactFamilies(pipeline)
|
|
origins := make(map[string]artifacts.EffectiveArtifactOrigin, len(catalog.Members))
|
|
for key, member := range catalog.Members {
|
|
origins[key] = artifacts.EffectiveArtifactOrigin{Family: member.Family, CharacterID: member.CharacterID}
|
|
}
|
|
return origins
|
|
}
|
|
|
|
func selectedArtifactName(err error) string {
|
|
message := err.Error()
|
|
start := strings.Index(message, "\"")
|
|
if start < 0 {
|
|
return ""
|
|
}
|
|
end := strings.Index(message[start+1:], "\"")
|
|
if end < 0 {
|
|
return ""
|
|
}
|
|
return message[start+1 : start+1+end]
|
|
}
|
|
|
|
func validateEffectiveArtifactConfiguration(configured map[string]config.ScriptoriumArtifactConfig, effective artifacts.EffectiveArtifactSet) error {
|
|
for _, name := range effective.Keys() {
|
|
artifactCfg := configured[name]
|
|
if strings.TrimSpace(artifactCfg.PromptID) == "" {
|
|
return fmt.Errorf("pipeline.scriptorium.artifacts.%s.prompt_id is required when selected", name)
|
|
}
|
|
if strings.TrimSpace(artifactCfg.OutputPath) == "" {
|
|
return fmt.Errorf("pipeline.scriptorium.artifacts.%s.output_path is required when selected", name)
|
|
}
|
|
}
|
|
return nil
|
|
}
|