package commandkit import ( "container/heap" "errors" "fmt" ) // ErrFlagInput identifies a missing required captured flag selected for an input. var ErrFlagInput = errors.New("required flag input is missing") // ErrFlagWiring identifies an invalid captured binding or destination. var ErrFlagWiring = errors.New("flag wiring is invalid") // FlagValues stores invocation-local converted values and presence for captured // flags. It does not retain an input instance; values can be applied to // independently initialized destinations. Captured occurrence flags retain // their relative command-line order across the selected bindings. type FlagValues[I any] struct { values map[*bindingIdentity]preparedValue[I] } // CaptureFlags adapts flags declared for I into flags on Outer. During parsing, // each adapted flag stores its prepared value in target, associated with the // original inner binding identity. Copies of an original handle remain // selectable; an independently constructed lookalike does not. func CaptureFlags[Outer, I any](flags []Flag[I], target func(*Outer) *FlagValues[I]) []Flag[Outer] { captured := make([]Flag[Outer], len(flags)) for index, original := range flags { inner := original identity := newBindingIdentity() binding := flagBinding[Outer]{ identity: identity, meta: inner.binding.meta, deferredRequired: inner.binding.meta.Required, defaultText: inner.binding.defaultText, repeated: inner.binding.repeated, boolean: inner.binding.boolean, } if target != nil && inner.binding.identity != nil && inner.binding.valid && inner.binding.prepare != nil { binding.valid = true binding.prepare = func(raw []string) (preparedValue[Outer], error) { prepared, err := inner.binding.prepare(raw) if err != nil { return preparedValue[Outer]{}, err } result := preparedValue[Outer]{ identity: identity, supplied: prepared.supplied, apply: func(outer *Outer) { store := target(outer) if store == nil { return } if store.values == nil { store.values = make(map[*bindingIdentity]preparedValue[I]) } store.values[inner.binding.identity] = prepared }, } for _, occurrence := range prepared.ordered { result.ordered = append(result.ordered, preparedOccurrence[Outer]{ordinal: occurrence.ordinal}) } return result, nil } } captured[index] = Flag[Outer]{binding: binding} } return captured } // ApplyTo applies selected original captured flags to an initialized // destination. A missing captured binding or nil destination matches // ErrFlagWiring. A required selected value that was not supplied matches // ErrFlagInput. Ordinary selected values are applied first, followed by selected // occurrence values in command-line order. Repeated core-owned values are copied // for each application. func (values FlagValues[I]) ApplyTo(target *I, selected ...Flag[I]) error { return values.applyTo(target, false, selected...) } // ApplySuppliedTo applies only explicitly supplied selected flags, preserving // the destination's existing values for omitted flags. It checks selected // bindings and required values like ApplyTo and preserves occurrence order. func (values FlagValues[I]) ApplySuppliedTo(target *I, selected ...Flag[I]) error { return values.applyTo(target, true, selected...) } func (values FlagValues[I]) applyTo(target *I, suppliedOnly bool, selected ...Flag[I]) error { if target == nil { return fmt.Errorf("%w: destination is nil", ErrFlagWiring) } preparedValues := make([]preparedValue[I], 0, len(selected)) for _, flag := range selected { binding := flag.binding if binding.identity == nil { return fmt.Errorf("%w: selected flag has no identity", ErrFlagWiring) } prepared, exists := values.values[binding.identity] if !exists { return fmt.Errorf("%w: flag --%s was not captured", ErrFlagWiring, binding.meta.Name) } if binding.meta.Required && !prepared.supplied { return fmt.Errorf("flag --%s: %w", binding.meta.Name, ErrFlagInput) } if suppliedOnly && !prepared.supplied { continue } preparedValues = append(preparedValues, prepared) } var ordered occurrenceHeap[I] for index, prepared := range preparedValues { if prepared.apply != nil { prepared.apply(target) } stream := occurrenceStream[I]{events: prepared.ordered, order: index} stream.skipUnapplied() if len(stream.events) > 0 { ordered = append(ordered, stream) } } // One selected stream already has the required order. if len(ordered) == 1 { for _, event := range ordered[0].events { if event.apply != nil { event.apply(target) } } return nil } heap.Init(&ordered) for len(ordered) > 0 { stream := &ordered[0] stream.events[0].apply(target) stream.events = stream.events[1:] stream.skipUnapplied() if len(stream.events) == 0 { heap.Pop(&ordered) } else { heap.Fix(&ordered, 0) } } return nil } // Each binding supplies a monotone stream. The selection index breaks ordinal // ties in the same order as concatenation followed by a stable sort. type occurrenceStream[I any] struct { events []preparedOccurrence[I] order int } func (stream *occurrenceStream[I]) skipUnapplied() { for len(stream.events) > 0 && stream.events[0].apply == nil { stream.events = stream.events[1:] } } type occurrenceHeap[I any] []occurrenceStream[I] func (h occurrenceHeap[I]) Len() int { return len(h) } func (h occurrenceHeap[I]) Less(i, j int) bool { left, right := *h[i].events[0].ordinal, *h[j].events[0].ordinal if left == right { return h[i].order < h[j].order } return left < right } func (h occurrenceHeap[I]) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func (h *occurrenceHeap[I]) Push(value any) { *h = append(*h, value.(occurrenceStream[I])) } func (h *occurrenceHeap[I]) Pop() any { last := len(*h) - 1 value := (*h)[last] (*h)[last] = occurrenceStream[I]{} *h = (*h)[:last] return value } func classifyInputError(err error) Outcome { if errors.Is(err, ErrFlagWiring) { return Failure(err.Error()).WithCause(err) } return Usage(err.Error()).WithCause(err) }