Preserve ordered flag occurrences
This commit is contained in:
76
binding.go
76
binding.go
@@ -3,6 +3,7 @@ package commandkit
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
commandmeta "go.maximumdirect.net/commandkit/internal/commandmeta"
|
||||
@@ -38,6 +39,48 @@ func ValueFlag[I, V any](meta FlagMeta, defaultValue *V, parse func(string) (V,
|
||||
return Flag[I]{binding: binding}
|
||||
}
|
||||
|
||||
// OccurrenceFlag declares a repeatable flag whose setter runs once for each
|
||||
// supplied occurrence. Occurrence setters run in command-line order across
|
||||
// participating bindings; an omitted flag does not call its setter.
|
||||
func OccurrenceFlag[I, V any](meta FlagMeta, parse func(string) (V, error), set func(*I, Supplied[V])) Flag[I] {
|
||||
binding := flagBinding[I]{identity: newBindingIdentity(), meta: meta, repeated: true}
|
||||
if parse == nil || set == nil {
|
||||
return Flag[I]{binding: binding}
|
||||
}
|
||||
binding.valid = true
|
||||
binding.prepare = func(values []string) (preparedValue[I], error) {
|
||||
result := preparedValue[I]{identity: binding.identity, supplied: len(values) > 0}
|
||||
for index, raw := range values {
|
||||
value, err := parse(raw)
|
||||
if err != nil {
|
||||
return preparedValue[I]{}, &flagOccurrenceError{index: index, cause: err}
|
||||
}
|
||||
ordinal := index
|
||||
result.ordered = append(result.ordered, preparedOccurrence[I]{ordinal: &ordinal, apply: func(input *I) { set(input, Supplied[V]{Value: value, Set: true}) }})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
return Flag[I]{binding: binding}
|
||||
}
|
||||
|
||||
// RenameFlag returns an independent binding with a new long name and no short
|
||||
// spelling. Parsing, defaults, completion, requiredness, and setters are retained.
|
||||
func RenameFlag[I any](flag Flag[I], name string) Flag[I] {
|
||||
binding := flag.binding
|
||||
binding.meta.Name = name
|
||||
binding.meta.Short = ""
|
||||
binding.identity = newBindingIdentity()
|
||||
if prepare := binding.prepare; prepare != nil {
|
||||
identity := binding.identity
|
||||
binding.prepare = func(values []string) (preparedValue[I], error) {
|
||||
value, err := prepare(values)
|
||||
value.identity = identity
|
||||
return value, err
|
||||
}
|
||||
}
|
||||
return Flag[I]{binding: binding}
|
||||
}
|
||||
|
||||
// BoolFlag declares a boolean flag. A bare flag is supplied by the parser as
|
||||
// "true"; explicit values are converted with strconv.ParseBool. Set distinguishes
|
||||
// an omitted flag from an explicit false value.
|
||||
@@ -189,7 +232,9 @@ func (p preparedInput[I]) build() I {
|
||||
input = p.newInput()
|
||||
}
|
||||
for _, value := range p.values {
|
||||
value.apply(&input)
|
||||
if value.apply != nil {
|
||||
value.apply(&input)
|
||||
}
|
||||
}
|
||||
return input
|
||||
}
|
||||
@@ -202,6 +247,10 @@ func prepareInput[I any](spec CommandSpec[I], flagValues [][]string, operands []
|
||||
}
|
||||
|
||||
func prepareInputWithSpellings[I any](spec CommandSpec[I], flagValues, flagSpellings [][]string, operands []string, enforceRequired bool) (preparedInput[I], error) {
|
||||
return prepareInputWithOccurrences(spec, flagValues, flagSpellings, nil, operands, enforceRequired)
|
||||
}
|
||||
|
||||
func prepareInputWithOccurrences[I any](spec CommandSpec[I], flagValues, flagSpellings [][]string, occurrences []commandmeta.FlagOccurrence, operands []string, enforceRequired bool) (preparedInput[I], error) {
|
||||
if len(flagValues) != len(spec.Flags) {
|
||||
return preparedInput[I]{}, fmt.Errorf("received %d flag value sets for %d bindings", len(flagValues), len(spec.Flags))
|
||||
}
|
||||
@@ -212,6 +261,12 @@ func prepareInputWithSpellings[I any](spec CommandSpec[I], flagValues, flagSpell
|
||||
return preparedInput[I]{}, fmt.Errorf("received %d flag spelling sets for %d bindings", len(flagSpellings), len(spec.Flags))
|
||||
}
|
||||
preparedFlags := make([]preparedValue[I], len(spec.Flags))
|
||||
ordinals := make(map[string][]int)
|
||||
for index, occurrence := range occurrences {
|
||||
ordinals[occurrence.Name] = append(ordinals[occurrence.Name], index)
|
||||
}
|
||||
var ordered []preparedOccurrence[I]
|
||||
fallbackOrdinal := 0
|
||||
missingRequired := ""
|
||||
for i, flag := range spec.Flags {
|
||||
binding := flag.binding
|
||||
@@ -231,6 +286,21 @@ func prepareInputWithSpellings[I any](spec CommandSpec[I], flagValues, flagSpell
|
||||
return preparedInput[I]{}, fmt.Errorf("flag --%s: %w", binding.meta.Name, err)
|
||||
}
|
||||
preparedFlags[i] = value
|
||||
for index, occurrence := range value.ordered {
|
||||
if occurrences != nil {
|
||||
positions := ordinals[binding.meta.Name]
|
||||
if index >= len(positions) {
|
||||
return preparedInput[I]{}, fmt.Errorf("missing occurrence position for flag --%s", binding.meta.Name)
|
||||
}
|
||||
*occurrence.ordinal = positions[index]
|
||||
} else {
|
||||
*occurrence.ordinal = fallbackOrdinal
|
||||
fallbackOrdinal++
|
||||
}
|
||||
if occurrence.apply != nil {
|
||||
ordered = append(ordered, occurrence)
|
||||
}
|
||||
}
|
||||
if enforceRequired && binding.meta.Required && !binding.deferredRequired && !value.supplied && missingRequired == "" {
|
||||
missingRequired = binding.meta.Name
|
||||
}
|
||||
@@ -243,6 +313,10 @@ func prepareInputWithSpellings[I any](spec CommandSpec[I], flagValues, flagSpell
|
||||
return preparedInput[I]{}, err
|
||||
}
|
||||
preparedValues := append([]preparedValue[I](nil), preparedFlags...)
|
||||
sort.SliceStable(ordered, func(i, j int) bool { return *ordered[i].ordinal < *ordered[j].ordinal })
|
||||
for _, occurrence := range ordered {
|
||||
preparedValues = append(preparedValues, preparedValue[I]{apply: occurrence.apply})
|
||||
}
|
||||
for i, argument := range spec.Arguments {
|
||||
binding := argument.binding
|
||||
value, err := binding.prepare(argumentValues[i])
|
||||
|
||||
Reference in New Issue
Block a user