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 }