Retain entered flag spellings in parser failures

This commit is contained in:
2026-10-20 00:29:49 +00:00
parent eb92f7be7f
commit 520d7e125b
5 changed files with 108 additions and 8 deletions

View File

@@ -1,12 +1,13 @@
package cobra
import (
"fmt"
"strings"
commandmeta "gitea.maximumdirect.net/eric/CommandKit/internal/commandmeta"
)
func interpretFlagTokens(argv []string, flags []commandmeta.Flag, aliases []commandmeta.FlagAlias) ([]string, map[string][]string) {
func interpretFlagTokens(argv []string, flags []commandmeta.Flag, aliases []commandmeta.FlagAlias) ([]string, map[string][]string, *commandmeta.Failure) {
canonical := make(map[string]commandmeta.Flag, len(flags)*2+2)
help := commandmeta.Flag{Name: "help", Short: "h", Boolean: true}
canonical["--help"] = help
@@ -61,6 +62,9 @@ func interpretFlagTokens(argv []string, flags []commandmeta.Flag, aliases []comm
index += 2
continue
}
if !flag.Boolean {
return interpreted, spellings, &commandmeta.Failure{Message: fmt.Sprintf("flag needs an argument: %s", original)}
}
interpreted = append(interpreted, "--"+flag.Name)
index++
continue
@@ -107,8 +111,7 @@ func interpretFlagTokens(argv []string, flags []commandmeta.Flag, aliases []comm
consumedNext = true
break
}
interpreted = append(interpreted, "--"+flag.Name)
break
return interpreted, spellings, &commandmeta.Failure{Message: fmt.Sprintf("flag needs an argument: %s", original)}
}
if !consumedNext {
index++
@@ -118,5 +121,5 @@ func interpretFlagTokens(argv []string, flags []commandmeta.Flag, aliases []comm
interpreted = append(interpreted, token)
index++
}
return interpreted, spellings
return interpreted, spellings, nil
}

View File

@@ -2,6 +2,7 @@ package cobra
import (
"reflect"
"strings"
"testing"
commandmeta "gitea.maximumdirect.net/eric/CommandKit/internal/commandmeta"
@@ -158,3 +159,40 @@ func TestParseFlagAliasOccurrenceOrderAndSpellings(t *testing.T) {
t.Fatalf("tag spellings = %#v, want %#v", got, want)
}
}
func TestParseFailuresRetainEnteredFlagSpellings(t *testing.T) {
for _, spelling := range []string{"--color", "--colour", "-c", "-C"} {
t.Run("missing/"+spelling, func(t *testing.T) {
result := Parse(flagGrammarTree(), []string{"run", spelling}, flagGrammarOptions())
if result.Failure == nil || !strings.Contains(result.Failure.Message, ": "+spelling) {
t.Fatalf("missing value failure = %#v, want entered spelling %q", result.Failure, spelling)
}
})
}
result := Parse(flagGrammarTree(), []string{"run", "-wC"}, flagGrammarOptions())
if result.Failure == nil || !strings.Contains(result.Failure.Message, ": -C") {
t.Fatalf("cluster missing value failure = %#v", result.Failure)
}
for _, test := range []struct {
args []string
spelling string
}{
{[]string{"--hlep=bad"}, "--hlep"},
{[]string{"-h=bad"}, "-h"},
{[]string{"--help=bad"}, "--help"},
{[]string{"--hlep=bad", "--help=true"}, "--hlep"},
{[]string{"-h=bad", "--hlep=true"}, "-h"},
{[]string{"--help=true", "--hlep=bad"}, "--hlep"},
} {
t.Run(strings.Join(test.args, " "), func(t *testing.T) {
result := Parse(flagGrammarTree(), append([]string{"run"}, test.args...), flagGrammarOptions())
if result.Failure == nil || !strings.Contains(result.Failure.Message, "for "+test.spelling) {
t.Fatalf("help failure = %#v, want entered spelling %q", result.Failure, test.spelling)
}
})
}
result = Parse(flagGrammarTree(), []string{"run", "--unknown", "--colour"}, flagGrammarOptions())
if result.Failure == nil || !strings.Contains(result.Failure.Message, "unknown") || strings.Contains(result.Failure.Message, "needs an argument") {
t.Fatalf("earlier parser failure = %#v", result.Failure)
}
}

View File

@@ -77,7 +77,7 @@ func (session *Session) Parse(argv []string) commandmeta.Result {
}
remaining := append(commandpath.CloneTokens(unconsumed), tail...)
interpreted, spellings := interpretFlagTokens(remaining, selectedNode.Flags, session.flagAliases)
interpreted, spellings, interpretationFailure := interpretFlagTokens(remaining, selectedNode.Flags, session.flagAliases)
for _, flag := range selectedNode.Flags {
if value := selected.Flags().Lookup(flag.Name); value != nil {
value.Value.(*rawValue).queueSpellings(spellings[flag.Name])
@@ -89,6 +89,12 @@ func (session *Session) Parse(argv []string) commandmeta.Result {
if err := selected.ParseFlags(interpreted); err != nil {
return failure(result, err.Error())
}
// Parse the completed prefix first so an earlier backend failure keeps its
// precedence over a missing value discovered while interpreting aliases.
if interpretationFailure != nil {
result.Failure = interpretationFailure
return result
}
operands := selected.Flags().Args()
result.Operands = append([]string(nil), operands...)
@@ -473,10 +479,14 @@ func (v *rawValue) spellingsCopy() []string { return append([]string(nil), v.spe
func (v *rawValue) lastBool() (bool, error) {
var value bool
for _, raw := range v.values {
for index, raw := range v.values {
parsed, err := strconv.ParseBool(raw)
if err != nil {
return false, fmt.Errorf("invalid boolean value %q for --help", raw)
spelling := "--help"
if index < len(v.spellings) {
spelling = v.spellings[index]
}
return false, fmt.Errorf("invalid boolean value %q for %s", raw, spelling)
}
value = parsed
}