268 lines
9.3 KiB
Go
268 lines
9.3 KiB
Go
package commandkit
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
)
|
|
|
|
// ValueFlag declares a singleton flag with a typed parser, optional default,
|
|
// and setter. Every supplied occurrence is parsed; the last parsed value is
|
|
// applied with Set=true.
|
|
func ValueFlag[I, V any](meta FlagMeta, defaultValue *V, parse func(string) (V, error), set func(*I, Supplied[V])) Flag[I] {
|
|
var fallback V
|
|
if defaultValue != nil {
|
|
fallback = *defaultValue
|
|
}
|
|
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) (preparedValue[I], error) {
|
|
value := fallback
|
|
supplied := len(values) > 0
|
|
for _, raw := range values {
|
|
parsed, err := parse(raw)
|
|
if err != nil {
|
|
return preparedValue[I]{}, err
|
|
}
|
|
value = parsed
|
|
}
|
|
result := Supplied[V]{Value: value, Set: supplied}
|
|
return preparedValue[I]{identity: binding.identity, supplied: supplied, apply: func(input *I) { set(input, result) }}, nil
|
|
}
|
|
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.
|
|
func BoolFlag[I any](meta FlagMeta, defaultValue bool, set func(*I, Supplied[bool])) Flag[I] {
|
|
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) (preparedValue[I], error) {
|
|
value := defaultValue
|
|
supplied := len(values) > 0
|
|
for _, raw := range values {
|
|
parsed, err := strconv.ParseBool(raw)
|
|
if err != nil {
|
|
return preparedValue[I]{}, err
|
|
}
|
|
value = parsed
|
|
}
|
|
result := Supplied[bool]{Value: value, Set: supplied}
|
|
return preparedValue[I]{identity: binding.identity, supplied: supplied, apply: func(input *I) { set(input, result) }}, nil
|
|
}
|
|
return Flag[I]{binding: binding}
|
|
}
|
|
|
|
// RepeatFlag declares a repeatable flag. Supplied occurrences replace defaults
|
|
// 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]{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) (preparedValue[I], error) {
|
|
parsedValues := append([]V(nil), fallback...)
|
|
supplied := len(values) > 0
|
|
if supplied {
|
|
parsedValues = make([]V, 0, len(values))
|
|
for _, raw := range values {
|
|
parsed, err := parse(raw)
|
|
if err != nil {
|
|
return preparedValue[I]{}, err
|
|
}
|
|
parsedValues = append(parsedValues, parsed)
|
|
}
|
|
}
|
|
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
|
|
}
|
|
return Flag[I]{binding: binding}
|
|
}
|
|
|
|
// ValueArgument declares an optional or required singleton positional
|
|
// argument.
|
|
func ValueArgument[I, V any](meta ArgumentMeta, required bool, parse func(string) (V, error), set func(*I, Supplied[V])) Argument[I] {
|
|
min := 0
|
|
if required {
|
|
min = 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) (preparedValue[I], error) {
|
|
if len(values) > 1 {
|
|
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 preparedValue[I]{}, err
|
|
}
|
|
value = parsed
|
|
}
|
|
result := Supplied[V]{Value: value, Set: supplied}
|
|
return preparedValue[I]{identity: binding.identity, supplied: supplied, apply: func(input *I) { set(input, result) }}, nil
|
|
}
|
|
return Argument[I]{binding: binding}
|
|
}
|
|
|
|
// 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]{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) (preparedValue[I], error) {
|
|
if max >= 0 && 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 preparedValue[I]{}, err
|
|
}
|
|
parsedValues = append(parsedValues, parsed)
|
|
}
|
|
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
|
|
}
|
|
return Argument[I]{binding: binding}
|
|
}
|
|
|
|
type preparedInput[I any] struct {
|
|
newInput func() I
|
|
values []preparedValue[I]
|
|
}
|
|
|
|
func (p preparedInput[I]) build() I {
|
|
var input I
|
|
if p.newInput != nil {
|
|
input = p.newInput()
|
|
}
|
|
for _, value := range p.values {
|
|
value.apply(&input)
|
|
}
|
|
return input
|
|
}
|
|
|
|
// prepareInput parses all supplied bindings into typed closures before it
|
|
// creates an input value. When enforceRequired is false, missing required flags
|
|
// and operands are allowed so metadata help can validate supplied syntax.
|
|
func prepareInput[I any](spec CommandSpec[I], flagValues [][]string, 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))
|
|
}
|
|
preparedFlags := make([]preparedValue[I], len(spec.Flags))
|
|
missingRequired := ""
|
|
for i, flag := range spec.Flags {
|
|
binding := flag.binding
|
|
value, err := binding.prepare(flagValues[i])
|
|
if err != nil {
|
|
return preparedInput[I]{}, fmt.Errorf("flag --%s: %w", binding.meta.Name, err)
|
|
}
|
|
preparedFlags[i] = value
|
|
if enforceRequired && binding.meta.Required && !binding.deferredRequired && !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
|
|
value, err := binding.prepare(argumentValues[i])
|
|
if err != nil {
|
|
return preparedInput[I]{}, fmt.Errorf("argument %q: %w", binding.meta.Name, err)
|
|
}
|
|
preparedValues = append(preparedValues, value)
|
|
}
|
|
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
|
|
if binding.min < 0 || binding.max < -1 || (binding.max >= 0 && binding.min > binding.max) {
|
|
return nil, fmt.Errorf("argument %q has invalid bounds %d..%d", binding.meta.Name, binding.min, binding.max)
|
|
}
|
|
}
|
|
if enforceRequired {
|
|
minimum := 0
|
|
for _, argument := range definitions {
|
|
if argument.binding.min > len(operands)-minimum {
|
|
return nil, fmt.Errorf("not enough positional arguments")
|
|
}
|
|
minimum += argument.binding.min
|
|
}
|
|
}
|
|
|
|
suffixMinimum := make([]int, len(definitions)+1)
|
|
for i := len(definitions) - 1; i >= 0; i-- {
|
|
minimum := definitions[i].binding.min
|
|
remaining := len(operands) - suffixMinimum[i+1]
|
|
if minimum > remaining {
|
|
suffixMinimum[i] = len(operands)
|
|
} else {
|
|
suffixMinimum[i] = suffixMinimum[i+1] + minimum
|
|
}
|
|
}
|
|
|
|
allocated := make([][]string, len(definitions))
|
|
position := 0
|
|
for i, argument := range definitions {
|
|
binding := argument.binding
|
|
remaining := len(operands) - position
|
|
available := remaining - suffixMinimum[i+1]
|
|
if available < 0 {
|
|
available = 0
|
|
}
|
|
count := available
|
|
if binding.max >= 0 && count > binding.max {
|
|
count = binding.max
|
|
}
|
|
if !enforceRequired && count < binding.min && remaining > 0 {
|
|
count = remaining
|
|
if binding.max >= 0 && count > binding.max {
|
|
count = binding.max
|
|
}
|
|
}
|
|
if enforceRequired && count < binding.min {
|
|
return nil, fmt.Errorf("not enough values for argument %q", binding.meta.Name)
|
|
}
|
|
allocated[i] = operands[position : position+count]
|
|
position += count
|
|
}
|
|
if position < len(operands) {
|
|
return nil, fmt.Errorf("received %d extra positional arguments", len(operands)-position)
|
|
}
|
|
return allocated, nil
|
|
}
|