Preserve alias context in usage errors

This commit is contained in:
2026-10-19 19:46:22 +00:00
parent a504efbad7
commit 1fa026e5bd
5 changed files with 239 additions and 13 deletions

View File

@@ -1,6 +1,7 @@
package commandkit
import (
"errors"
"fmt"
"strconv"
)
@@ -22,10 +23,10 @@ func ValueFlag[I, V any](meta FlagMeta, defaultValue *V, parse func(string) (V,
binding.prepare = func(values []string) (preparedValue[I], error) {
value := fallback
supplied := len(values) > 0
for _, raw := range values {
for index, raw := range values {
parsed, err := parse(raw)
if err != nil {
return preparedValue[I]{}, err
return preparedValue[I]{}, &flagOccurrenceError{index: index, cause: err}
}
value = parsed
}
@@ -47,10 +48,10 @@ func BoolFlag[I any](meta FlagMeta, defaultValue bool, set func(*I, Supplied[boo
binding.prepare = func(values []string) (preparedValue[I], error) {
value := defaultValue
supplied := len(values) > 0
for _, raw := range values {
for index, raw := range values {
parsed, err := strconv.ParseBool(raw)
if err != nil {
return preparedValue[I]{}, err
return preparedValue[I]{}, &flagOccurrenceError{index: index, cause: err}
}
value = parsed
}
@@ -76,10 +77,10 @@ func RepeatFlag[I, V any](meta FlagMeta, defaults []V, parse func(string) (V, er
supplied := len(values) > 0
if supplied {
parsedValues = make([]V, 0, len(values))
for _, raw := range values {
for index, raw := range values {
parsed, err := parse(raw)
if err != nil {
return preparedValue[I]{}, err
return preparedValue[I]{}, &flagOccurrenceError{index: index, cause: err}
}
parsedValues = append(parsedValues, parsed)
}
@@ -158,6 +159,28 @@ type preparedInput[I any] struct {
values []preparedValue[I]
}
type flagOccurrenceError struct {
index int
cause error
}
func (err *flagOccurrenceError) Error() string { return err.cause.Error() }
func (err *flagOccurrenceError) Unwrap() error { return err.cause }
type flagConversionError struct {
binding string
spelling string
occurrence int
cause error
}
func (err *flagConversionError) Error() string {
return fmt.Sprintf("flag %s occurrence %d: %v", err.spelling, err.occurrence, err.cause)
}
func (err *flagConversionError) Unwrap() error { return err.cause }
func (p preparedInput[I]) build() I {
var input I
if p.newInput != nil {
@@ -173,15 +196,36 @@ func (p preparedInput[I]) build() I {
// 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) {
return prepareInputWithSpellings(spec, flagValues, nil, operands, enforceRequired)
}
func prepareInputWithSpellings[I any](spec CommandSpec[I], flagValues, flagSpellings [][]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))
}
if flagSpellings == nil {
flagSpellings = make([][]string, len(spec.Flags))
}
if len(flagSpellings) != len(spec.Flags) {
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))
missingRequired := ""
for i, flag := range spec.Flags {
binding := flag.binding
value, err := binding.prepare(flagValues[i])
if err != nil {
var occurrenceErr *flagOccurrenceError
if errors.As(err, &occurrenceErr) {
spelling := "--" + binding.meta.Name
if occurrenceErr.index >= 0 && occurrenceErr.index < len(flagSpellings[i]) && flagSpellings[i][occurrenceErr.index] != "" {
spelling = flagSpellings[i][occurrenceErr.index]
}
return preparedInput[I]{}, &flagConversionError{
binding: binding.meta.Name, spelling: spelling,
occurrence: occurrenceErr.index + 1, cause: occurrenceErr.cause,
}
}
return preparedInput[I]{}, fmt.Errorf("flag --%s: %w", binding.meta.Name, err)
}
preparedFlags[i] = value