@@ -2,6 +2,7 @@ package raycicmd
22
33import (
44 "fmt"
5+ "maps"
56 "slices"
67)
78
@@ -113,6 +114,27 @@ func expandSingleArrayStep(
113114 )
114115 }
115116
117+ // Parse and apply adjustments if present.
118+ var adjustments []* arrayAdjustment
119+ if arrayMap , ok := arrayDef .(map [string ]any ); ok {
120+ if adjDef , ok := arrayMap ["adjustments" ]; ok {
121+ adjustments , err = parseArrayAdjustments (adjDef )
122+ if err != nil {
123+ return nil , fmt .Errorf (
124+ "step %q: %w" , baseKey , err ,
125+ )
126+ }
127+ }
128+ }
129+
130+ elements , err = applyAdjustments (elements , adjustments , cfg )
131+ if err != nil {
132+ return nil , fmt .Errorf (
133+ "step %q: %w" , baseKey , err ,
134+ )
135+ }
136+
137+ cfg .elements = elements
116138 configs [baseKey ] = cfg
117139
118140 var result []map [string ]any
@@ -137,6 +159,78 @@ func expandSingleArrayStep(
137159 return result , nil
138160}
139161
162+ // applyAdjustments processes adjustments in two passes: first appends
163+ // additions, then filters out skipped elements.
164+ func applyAdjustments (
165+ elements []* arrayElement ,
166+ adjustments []* arrayAdjustment ,
167+ cfg * arrayConfig ,
168+ ) ([]* arrayElement , error ) {
169+ // Pass 1: append additions.
170+ for _ , adj := range adjustments {
171+ if adj .skip {
172+ continue
173+ }
174+ for dim := range cfg .dims {
175+ if _ , ok := adj .with [dim ]; ! ok {
176+ return nil , fmt .Errorf (
177+ "addition adjustment must specify all " +
178+ "dimensions; missing %q" , dim ,
179+ )
180+ }
181+ }
182+ elements = append (elements , & arrayElement {
183+ values : maps .Clone (adj .with ),
184+ })
185+ }
186+
187+ // Pass 2: collect skip adjustments, validate, then remove.
188+ var skipAdjs []* arrayAdjustment
189+ for _ , adj := range adjustments {
190+ if adj .skip {
191+ skipAdjs = append (skipAdjs , adj )
192+ }
193+ }
194+ for _ , adj := range skipAdjs {
195+ if ! hasElement (elements , adj .with ) {
196+ return nil , fmt .Errorf (
197+ "skip adjustment with=%v matches no element" ,
198+ adj .with ,
199+ )
200+ }
201+ }
202+ elements = slices .DeleteFunc (elements , func (e * arrayElement ) bool {
203+ for _ , adj := range skipAdjs {
204+ if elemMatchesWith (e , adj .with ) {
205+ return true
206+ }
207+ }
208+ return false
209+ })
210+
211+ return elements , nil
212+ }
213+
214+ func hasElement (
215+ elements []* arrayElement , with map [string ]string ,
216+ ) bool {
217+ for _ , elem := range elements {
218+ if elemMatchesWith (elem , with ) {
219+ return true
220+ }
221+ }
222+ return false
223+ }
224+
225+ func elemMatchesWith (elem * arrayElement , with map [string ]string ) bool {
226+ for dim , val := range with {
227+ if elem .values [dim ] != val {
228+ return false
229+ }
230+ }
231+ return true
232+ }
233+
140234// arraySelector represents a dependency selector with optional array filter.
141235type arraySelector struct {
142236 key string // step identifier: "key" for command steps, "name" for wanda steps
@@ -271,7 +365,8 @@ func resolveDependsOn(dependsOn any, configs map[string]*arrayConfig) ([]string,
271365 return result , nil
272366}
273367
274- // resolveArraySelector resolves a selector against an array config to concrete step keys.
368+ // resolveArraySelector resolves a selector against an array config
369+ // to concrete step keys, using the final element list (post-adjustments).
275370func resolveArraySelector (sel * arraySelector , cfg * arrayConfig ) ([]string , error ) {
276371 for dim := range sel .filter {
277372 if _ , ok := cfg .dims [dim ]; ! ok {
@@ -282,9 +377,8 @@ func resolveArraySelector(sel *arraySelector, cfg *arrayConfig) ([]string, error
282377 }
283378 }
284379
285- elements := cfg .expand ()
286380 var matches []string
287- for _ , elem := range elements {
381+ for _ , elem := range cfg . elements {
288382 if elem .matchesSelector (sel ) {
289383 matches = append (matches , elem .generateKey (sel .key ))
290384 }
0 commit comments