package artifacts import ( "fmt" "sort" "strings" "gitea.maximumdirect.net/eric/narratio/internal/artifactpolicy" "gitea.maximumdirect.net/eric/narratio/internal/config" ) // EffectiveArtifactSet is the resolved set of configured artifacts that an // analyze invocation will execute. type EffectiveArtifactSet struct { keys []string origins map[string]EffectiveArtifactOrigin resolved bool } // EffectiveArtifactOrigin identifies a concrete artifact produced by a // resolved family declaration. type EffectiveArtifactOrigin struct { Family string CharacterID string } // ResolveEffectiveArtifactSet applies an explicit artifact selection when one // is supplied; otherwise it selects the configured enabled artifacts. func ResolveEffectiveArtifactSet( configured map[string]ConfiguredArtifactDefinition, selected []string, ) (EffectiveArtifactSet, error) { configuredKeys := make(map[string]ConfiguredArtifactDefinition, len(configured)) for key, definition := range configured { trimmed := strings.TrimSpace(key) if !artifactpolicy.IsConfiguredKey(trimmed) { return EffectiveArtifactSet{}, fmt.Errorf("configured artifact key %q must match ^[a-z][a-z0-9_]*$", key) } configuredKeys[trimmed] = definition } if len(selected) > 0 { set := make(map[string]struct{}, len(selected)) for _, key := range selected { trimmed := strings.TrimSpace(key) if !artifactpolicy.IsConfiguredKey(trimmed) { return EffectiveArtifactSet{}, fmt.Errorf("selected artifact key %q must match ^[a-z][a-z0-9_]*$", key) } if _, ok := configuredKeys[trimmed]; !ok { return EffectiveArtifactSet{}, fmt.Errorf("selected artifact %q is not configured", trimmed) } set[trimmed] = struct{}{} } return newEffectiveArtifactSet(set), nil } set := make(map[string]struct{}, len(configuredKeys)) for key, definition := range configuredKeys { if definition.Enabled { set[key] = struct{}{} } } return newEffectiveArtifactSet(set), nil } // ConfiguredArtifactDefinitions converts Scriptorium configuration into the // definition form used by the runtime catalog. func ConfiguredArtifactDefinitions(items map[string]config.ScriptoriumArtifactConfig) map[string]ConfiguredArtifactDefinition { if len(items) == 0 { return nil } definitions := make(map[string]ConfiguredArtifactDefinition, len(items)) for key, item := range items { definitions[key] = ConfiguredArtifactDefinition{ Enabled: item.Enabled, OutputPath: item.OutputPath, } } return definitions } // Keys returns the selected configured artifact keys in deterministic order. func (s EffectiveArtifactSet) Keys() []string { return append([]string(nil), s.keys...) } // Includes reports whether a configured artifact belongs to the effective set. func (s EffectiveArtifactSet) Includes(key string) bool { trimmed := strings.TrimSpace(key) index := sort.SearchStrings(s.keys, trimmed) return index < len(s.keys) && s.keys[index] == trimmed } // Resolved reports whether the set was created by ResolveEffectiveArtifactSet. func (s EffectiveArtifactSet) Resolved() bool { return s.resolved } // WithOrigins attaches optional resolution provenance without changing the // selected concrete keys or lookup semantics. func (s EffectiveArtifactSet) WithOrigins(origins map[string]EffectiveArtifactOrigin) EffectiveArtifactSet { if len(origins) == 0 { return s } s.origins = make(map[string]EffectiveArtifactOrigin, len(origins)) for key, origin := range origins { if s.Includes(key) { s.origins[key] = origin } } return s } // Origin reports optional family provenance for a concrete artifact key. func (s EffectiveArtifactSet) Origin(key string) (EffectiveArtifactOrigin, bool) { origin, ok := s.origins[strings.TrimSpace(key)] return origin, ok } func newEffectiveArtifactSet(set map[string]struct{}) EffectiveArtifactSet { keys := make([]string, 0, len(set)) for key := range set { keys = append(keys, key) } sort.Strings(keys) return EffectiveArtifactSet{keys: keys, resolved: true} }