Unify typed binding preparation
This commit is contained in:
79
binding.go
79
binding.go
@@ -13,24 +13,24 @@ func ValueFlag[I, V any](meta FlagMeta, defaultValue *V, parse func(string) (V,
|
||||
if defaultValue != nil {
|
||||
fallback = *defaultValue
|
||||
}
|
||||
binding := flagBinding[I]{meta: meta, defaultText: fmt.Sprint(fallback)}
|
||||
binding := flagBinding[I]{identity: newBindingIdentity(), meta: meta, defaultText: fmt.Sprint(fallback)}
|
||||
if parse == nil || set == nil {
|
||||
return Flag[I]{binding: binding}
|
||||
}
|
||||
binding.valid = true
|
||||
binding.clear = func(input *I) { set(input, Supplied[V]{}) }
|
||||
binding.prepare = func(values []string) (func(*I), error) {
|
||||
binding.prepare = func(values []string) (preparedValue[I], error) {
|
||||
value := fallback
|
||||
supplied := len(values) > 0
|
||||
for _, raw := range values {
|
||||
parsed, err := parse(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return preparedValue[I]{}, err
|
||||
}
|
||||
value = parsed
|
||||
}
|
||||
result := Supplied[V]{Value: value, Set: supplied}
|
||||
return func(input *I) { set(input, result) }, nil
|
||||
return preparedValue[I]{identity: binding.identity, supplied: supplied, apply: func(input *I) { set(input, result) }}, nil
|
||||
}
|
||||
return Flag[I]{binding: binding}
|
||||
}
|
||||
@@ -38,24 +38,24 @@ func ValueFlag[I, V any](meta FlagMeta, defaultValue *V, parse func(string) (V,
|
||||
// BoolFlag declares a boolean flag. A bare flag is supplied by the parser as
|
||||
// "true"; explicit values are converted with strconv.ParseBool.
|
||||
func BoolFlag[I any](meta FlagMeta, defaultValue bool, set func(*I, Supplied[bool])) Flag[I] {
|
||||
binding := flagBinding[I]{meta: meta, defaultText: fmt.Sprint(defaultValue), boolean: true}
|
||||
binding := flagBinding[I]{identity: newBindingIdentity(), meta: meta, defaultText: fmt.Sprint(defaultValue), boolean: true}
|
||||
if set == nil {
|
||||
return Flag[I]{binding: binding}
|
||||
}
|
||||
binding.valid = true
|
||||
binding.clear = func(input *I) { set(input, Supplied[bool]{}) }
|
||||
binding.prepare = func(values []string) (func(*I), error) {
|
||||
binding.prepare = func(values []string) (preparedValue[I], error) {
|
||||
value := defaultValue
|
||||
supplied := len(values) > 0
|
||||
for _, raw := range values {
|
||||
parsed, err := strconv.ParseBool(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return preparedValue[I]{}, err
|
||||
}
|
||||
value = parsed
|
||||
}
|
||||
result := Supplied[bool]{Value: value, Set: supplied}
|
||||
return func(input *I) { set(input, result) }, nil
|
||||
return preparedValue[I]{identity: binding.identity, supplied: supplied, apply: func(input *I) { set(input, result) }}, nil
|
||||
}
|
||||
return Flag[I]{binding: binding}
|
||||
}
|
||||
@@ -64,13 +64,13 @@ func BoolFlag[I any](meta FlagMeta, defaultValue bool, set func(*I, Supplied[boo
|
||||
// and are parsed in argv order, with each occurrence treated as one value.
|
||||
func RepeatFlag[I, V any](meta FlagMeta, defaults []V, parse func(string) (V, error), set func(*I, Supplied[[]V])) Flag[I] {
|
||||
fallback := append([]V(nil), defaults...)
|
||||
binding := flagBinding[I]{meta: meta, defaultText: fmt.Sprint(fallback), repeated: true}
|
||||
binding := flagBinding[I]{identity: newBindingIdentity(), meta: meta, defaultText: fmt.Sprint(fallback), repeated: true}
|
||||
if parse == nil || set == nil {
|
||||
return Flag[I]{binding: binding}
|
||||
}
|
||||
binding.valid = true
|
||||
binding.clear = func(input *I) { set(input, Supplied[[]V]{}) }
|
||||
binding.prepare = func(values []string) (func(*I), error) {
|
||||
binding.prepare = func(values []string) (preparedValue[I], error) {
|
||||
parsedValues := append([]V(nil), fallback...)
|
||||
supplied := len(values) > 0
|
||||
if supplied {
|
||||
@@ -78,15 +78,15 @@ func RepeatFlag[I, V any](meta FlagMeta, defaults []V, parse func(string) (V, er
|
||||
for _, raw := range values {
|
||||
parsed, err := parse(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return preparedValue[I]{}, err
|
||||
}
|
||||
parsedValues = append(parsedValues, parsed)
|
||||
}
|
||||
}
|
||||
return func(input *I) {
|
||||
return preparedValue[I]{identity: binding.identity, supplied: supplied, apply: func(input *I) {
|
||||
result := Supplied[[]V]{Value: append([]V(nil), parsedValues...), Set: supplied}
|
||||
set(input, result)
|
||||
}, nil
|
||||
}}, nil
|
||||
}
|
||||
return Flag[I]{binding: binding}
|
||||
}
|
||||
@@ -98,26 +98,26 @@ func ValueArgument[I, V any](meta ArgumentMeta, required bool, parse func(string
|
||||
if required {
|
||||
min = 1
|
||||
}
|
||||
binding := argumentBinding[I]{meta: meta, min: min, max: 1}
|
||||
binding := argumentBinding[I]{identity: newBindingIdentity(), meta: meta, min: min, max: 1}
|
||||
if parse == nil || set == nil {
|
||||
return Argument[I]{binding: binding}
|
||||
}
|
||||
binding.valid = true
|
||||
binding.prepare = func(values []string) (func(*I), error) {
|
||||
binding.prepare = func(values []string) (preparedValue[I], error) {
|
||||
if len(values) > 1 {
|
||||
return nil, fmt.Errorf("received %d values; maximum is 1", len(values))
|
||||
return preparedValue[I]{}, fmt.Errorf("received %d values; maximum is 1", len(values))
|
||||
}
|
||||
var value V
|
||||
supplied := len(values) == 1
|
||||
if supplied {
|
||||
parsed, err := parse(values[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return preparedValue[I]{}, err
|
||||
}
|
||||
value = parsed
|
||||
}
|
||||
result := Supplied[V]{Value: value, Set: supplied}
|
||||
return func(input *I) { set(input, result) }, nil
|
||||
return preparedValue[I]{identity: binding.identity, supplied: supplied, apply: func(input *I) { set(input, result) }}, nil
|
||||
}
|
||||
return Argument[I]{binding: binding}
|
||||
}
|
||||
@@ -125,34 +125,34 @@ func ValueArgument[I, V any](meta ArgumentMeta, required bool, parse func(string
|
||||
// RepeatArgument declares a positional argument that accepts a bounded number
|
||||
// of values. A maximum of -1 means unbounded.
|
||||
func RepeatArgument[I, V any](meta ArgumentMeta, min, max int, parse func(string) (V, error), set func(*I, Supplied[[]V])) Argument[I] {
|
||||
binding := argumentBinding[I]{meta: meta, min: min, max: max}
|
||||
binding := argumentBinding[I]{identity: newBindingIdentity(), meta: meta, min: min, max: max}
|
||||
if parse == nil || set == nil {
|
||||
return Argument[I]{binding: binding}
|
||||
}
|
||||
binding.valid = true
|
||||
binding.prepare = func(values []string) (func(*I), error) {
|
||||
binding.prepare = func(values []string) (preparedValue[I], error) {
|
||||
if max >= 0 && len(values) > max {
|
||||
return nil, fmt.Errorf("received %d values; maximum is %d", len(values), max)
|
||||
return preparedValue[I]{}, fmt.Errorf("received %d values; maximum is %d", len(values), max)
|
||||
}
|
||||
parsedValues := make([]V, 0, len(values))
|
||||
for _, raw := range values {
|
||||
parsed, err := parse(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return preparedValue[I]{}, err
|
||||
}
|
||||
parsedValues = append(parsedValues, parsed)
|
||||
}
|
||||
return func(input *I) {
|
||||
return preparedValue[I]{identity: binding.identity, supplied: len(values) > 0, apply: func(input *I) {
|
||||
result := Supplied[[]V]{Value: append([]V(nil), parsedValues...), Set: len(values) > 0}
|
||||
set(input, result)
|
||||
}, nil
|
||||
}}, nil
|
||||
}
|
||||
return Argument[I]{binding: binding}
|
||||
}
|
||||
|
||||
type preparedInput[I any] struct {
|
||||
newInput func() I
|
||||
setters []func(*I)
|
||||
values []preparedValue[I]
|
||||
}
|
||||
|
||||
func (p preparedInput[I]) build() I {
|
||||
@@ -160,8 +160,8 @@ func (p preparedInput[I]) build() I {
|
||||
if p.newInput != nil {
|
||||
input = p.newInput()
|
||||
}
|
||||
for _, setter := range p.setters {
|
||||
setter(&input)
|
||||
for _, value := range p.values {
|
||||
value.apply(&input)
|
||||
}
|
||||
return input
|
||||
}
|
||||
@@ -173,33 +173,40 @@ func prepareInput[I any](spec CommandSpec[I], flagValues [][]string, operands []
|
||||
if len(flagValues) != len(spec.Flags) {
|
||||
return preparedInput[I]{}, fmt.Errorf("received %d flag value sets for %d bindings", len(flagValues), len(spec.Flags))
|
||||
}
|
||||
setters := make([]func(*I), 0, len(spec.Flags)+len(spec.Arguments))
|
||||
preparedFlags := make([]preparedValue[I], len(spec.Flags))
|
||||
missingRequired := ""
|
||||
for i, flag := range spec.Flags {
|
||||
binding := flag.binding
|
||||
if enforceRequired && binding.meta.Required && len(flagValues[i]) == 0 {
|
||||
return preparedInput[I]{}, fmt.Errorf("required flag --%s was not supplied", binding.meta.Name)
|
||||
}
|
||||
setter, err := binding.prepare(flagValues[i])
|
||||
value, err := binding.prepare(flagValues[i])
|
||||
if err != nil {
|
||||
return preparedInput[I]{}, fmt.Errorf("flag --%s: %w", binding.meta.Name, err)
|
||||
}
|
||||
setters = append(setters, setter)
|
||||
preparedFlags[i] = value
|
||||
if enforceRequired && binding.meta.Required && !value.supplied && missingRequired == "" {
|
||||
missingRequired = binding.meta.Name
|
||||
}
|
||||
}
|
||||
if missingRequired != "" {
|
||||
return preparedInput[I]{}, fmt.Errorf("required flag --%s was not supplied", missingRequired)
|
||||
}
|
||||
argumentValues, err := allocateOperands(operands, spec.Arguments, enforceRequired)
|
||||
if err != nil {
|
||||
return preparedInput[I]{}, err
|
||||
}
|
||||
preparedValues := append([]preparedValue[I](nil), preparedFlags...)
|
||||
for i, argument := range spec.Arguments {
|
||||
binding := argument.binding
|
||||
setter, err := binding.prepare(argumentValues[i])
|
||||
value, err := binding.prepare(argumentValues[i])
|
||||
if err != nil {
|
||||
return preparedInput[I]{}, fmt.Errorf("argument %q: %w", binding.meta.Name, err)
|
||||
}
|
||||
setters = append(setters, setter)
|
||||
preparedValues = append(preparedValues, value)
|
||||
}
|
||||
return preparedInput[I]{newInput: spec.NewInput, setters: setters}, nil
|
||||
return preparedInput[I]{newInput: spec.NewInput, values: preparedValues}, nil
|
||||
}
|
||||
|
||||
func newBindingIdentity() *bindingIdentity { return &bindingIdentity{marker: 1} }
|
||||
|
||||
func allocateOperands[I any](operands []string, definitions []Argument[I], enforceRequired bool) ([][]string, error) {
|
||||
for _, argument := range definitions {
|
||||
binding := argument.binding
|
||||
|
||||
Reference in New Issue
Block a user