Preserve reporting flag occurrence order
This commit is contained in:
@@ -7,7 +7,7 @@ import (
|
||||
commandmeta "gitea.maximumdirect.net/eric/CommandKit/internal/commandmeta"
|
||||
)
|
||||
|
||||
func interpretFlagTokens(argv []string, flags []commandmeta.Flag, aliases []commandmeta.FlagAlias) ([]string, map[string][]string, *commandmeta.Failure) {
|
||||
func interpretFlagTokens(argv []string, flags []commandmeta.Flag, aliases []commandmeta.FlagAlias) ([]string, map[string][]string, []commandmeta.FlagOccurrence, *commandmeta.Failure) {
|
||||
canonical := make(map[string]commandmeta.Flag, len(flags)*2+2)
|
||||
help := commandmeta.Flag{Name: "help", Short: "h", Boolean: true}
|
||||
canonical["--help"] = help
|
||||
@@ -27,7 +27,9 @@ func interpretFlagTokens(argv []string, flags []commandmeta.Flag, aliases []comm
|
||||
}
|
||||
|
||||
spellings := make(map[string][]string)
|
||||
occurrences := make([]commandmeta.FlagOccurrence, 0, len(argv))
|
||||
interpreted := make([]string, 0, len(argv))
|
||||
recordingOccurrences := true
|
||||
for index := 0; index < len(argv); {
|
||||
token := argv[index]
|
||||
if token == "--" {
|
||||
@@ -48,22 +50,34 @@ func interpretFlagTokens(argv []string, flags []commandmeta.Flag, aliases []comm
|
||||
}
|
||||
if !ok {
|
||||
interpreted = append(interpreted, token)
|
||||
recordingOccurrences = false
|
||||
index++
|
||||
continue
|
||||
}
|
||||
spellings[flag.Name] = append(spellings[flag.Name], original)
|
||||
if equal >= 0 {
|
||||
interpreted = append(interpreted, "--"+flag.Name+"="+body[equal+1:])
|
||||
value := body[equal+1:]
|
||||
interpreted = append(interpreted, "--"+flag.Name+"="+value)
|
||||
if recordingOccurrences {
|
||||
occurrences = append(occurrences, commandmeta.FlagOccurrence{Name: flag.Name, Spelling: original, RawValue: value})
|
||||
}
|
||||
index++
|
||||
continue
|
||||
}
|
||||
if !flag.Boolean && index+1 < len(argv) {
|
||||
interpreted = append(interpreted, "--"+flag.Name+"="+argv[index+1])
|
||||
value := argv[index+1]
|
||||
interpreted = append(interpreted, "--"+flag.Name+"="+value)
|
||||
if recordingOccurrences {
|
||||
occurrences = append(occurrences, commandmeta.FlagOccurrence{Name: flag.Name, Spelling: original, RawValue: value})
|
||||
}
|
||||
index += 2
|
||||
continue
|
||||
}
|
||||
if !flag.Boolean {
|
||||
return interpreted, spellings, &commandmeta.Failure{Message: fmt.Sprintf("flag needs an argument: %s", original)}
|
||||
return interpreted, spellings, occurrences, &commandmeta.Failure{Message: fmt.Sprintf("flag needs an argument: %s", original)}
|
||||
}
|
||||
if recordingOccurrences {
|
||||
occurrences = append(occurrences, commandmeta.FlagOccurrence{Name: flag.Name, Spelling: original, RawValue: "true"})
|
||||
}
|
||||
interpreted = append(interpreted, "--"+flag.Name)
|
||||
index++
|
||||
@@ -84,15 +98,23 @@ func interpretFlagTokens(argv []string, flags []commandmeta.Flag, aliases []comm
|
||||
}
|
||||
if !ok {
|
||||
interpreted = append(interpreted, "-"+token[offset:])
|
||||
recordingOccurrences = false
|
||||
break
|
||||
}
|
||||
spellings[flag.Name] = append(spellings[flag.Name], original)
|
||||
suffix := token[offset+1:]
|
||||
if flag.Boolean {
|
||||
if strings.HasPrefix(suffix, "=") {
|
||||
interpreted = append(interpreted, "--"+flag.Name+"="+suffix[1:])
|
||||
value := suffix[1:]
|
||||
interpreted = append(interpreted, "--"+flag.Name+"="+value)
|
||||
if recordingOccurrences {
|
||||
occurrences = append(occurrences, commandmeta.FlagOccurrence{Name: flag.Name, Spelling: original, RawValue: value})
|
||||
}
|
||||
break
|
||||
}
|
||||
if recordingOccurrences {
|
||||
occurrences = append(occurrences, commandmeta.FlagOccurrence{Name: flag.Name, Spelling: original, RawValue: "true"})
|
||||
}
|
||||
interpreted = append(interpreted, "--"+flag.Name)
|
||||
offset++
|
||||
continue
|
||||
@@ -103,15 +125,22 @@ func interpretFlagTokens(argv []string, flags []commandmeta.Flag, aliases []comm
|
||||
value = value[1:]
|
||||
}
|
||||
interpreted = append(interpreted, "--"+flag.Name+"="+value)
|
||||
if recordingOccurrences {
|
||||
occurrences = append(occurrences, commandmeta.FlagOccurrence{Name: flag.Name, Spelling: original, RawValue: value})
|
||||
}
|
||||
break
|
||||
}
|
||||
if index+1 < len(argv) {
|
||||
interpreted = append(interpreted, "--"+flag.Name+"="+argv[index+1])
|
||||
value := argv[index+1]
|
||||
interpreted = append(interpreted, "--"+flag.Name+"="+value)
|
||||
if recordingOccurrences {
|
||||
occurrences = append(occurrences, commandmeta.FlagOccurrence{Name: flag.Name, Spelling: original, RawValue: value})
|
||||
}
|
||||
index += 2
|
||||
consumedNext = true
|
||||
break
|
||||
}
|
||||
return interpreted, spellings, &commandmeta.Failure{Message: fmt.Sprintf("flag needs an argument: %s", original)}
|
||||
return interpreted, spellings, occurrences, &commandmeta.Failure{Message: fmt.Sprintf("flag needs an argument: %s", original)}
|
||||
}
|
||||
if !consumedNext {
|
||||
index++
|
||||
@@ -121,5 +150,5 @@ func interpretFlagTokens(argv []string, flags []commandmeta.Flag, aliases []comm
|
||||
interpreted = append(interpreted, token)
|
||||
index++
|
||||
}
|
||||
return interpreted, spellings, nil
|
||||
return interpreted, spellings, occurrences, nil
|
||||
}
|
||||
|
||||
@@ -32,6 +32,74 @@ func flagGrammarOptions() ParseOptions {
|
||||
}}
|
||||
}
|
||||
|
||||
func reportingFlagTree() *commandmeta.Command {
|
||||
return &commandmeta.Command{Name: "app", Children: []*commandmeta.Command{{
|
||||
Name: "run", Runnable: true, CoreFlags: commandmeta.ReportingFlags(),
|
||||
Flags: []commandmeta.Flag{{Name: "message"}},
|
||||
}}}
|
||||
}
|
||||
|
||||
func reportingFlagOptions() ParseOptions {
|
||||
return ParseOptions{FlagAliases: []commandmeta.FlagAlias{
|
||||
{Source: "-q", Target: "--quiet"},
|
||||
{Source: "-v", Target: "--verbose"},
|
||||
{Source: "-d", Target: "--debug"},
|
||||
}}
|
||||
}
|
||||
|
||||
func TestParseReportsFlagOccurrencesInEnteredOrder(t *testing.T) {
|
||||
result := Parse(reportingFlagTree(), []string{
|
||||
"run", "--verbose=true", "-qd", "--progress=false", "--message=--quiet", "--", "--debug",
|
||||
}, reportingFlagOptions())
|
||||
if result.Failure != nil {
|
||||
t.Fatalf("parse failed: %s", result.Failure.Message)
|
||||
}
|
||||
want := []commandmeta.FlagOccurrence{
|
||||
{Name: "verbose", Spelling: "--verbose", RawValue: "true"},
|
||||
{Name: "quiet", Spelling: "-q", RawValue: "true"},
|
||||
{Name: "debug", Spelling: "-d", RawValue: "true"},
|
||||
{Name: "progress", Spelling: "--progress", RawValue: "false"},
|
||||
{Name: "message", Spelling: "--message", RawValue: "--quiet"},
|
||||
}
|
||||
if !reflect.DeepEqual(result.FlagOccurrences, want) {
|
||||
t.Fatalf("flag occurrences = %#v, want %#v", result.FlagOccurrences, want)
|
||||
}
|
||||
if !reflect.DeepEqual(result.FlagValues, [][]string{{"--quiet"}}) || !reflect.DeepEqual(result.FlagSpellings, [][]string{{"--message"}}) {
|
||||
t.Fatalf("consumer values or spellings changed: %#v, %#v", result.FlagValues, result.FlagSpellings)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFlagOccurrencesRetainOnlyConsumedPrefixOnFailure(t *testing.T) {
|
||||
result := Parse(reportingFlagTree(), []string{"run", "--verbose", "--unknown", "--quiet"}, reportingFlagOptions())
|
||||
if result.Failure == nil || len(result.FlagOccurrences) != 1 || result.FlagOccurrences[0].Name != "verbose" {
|
||||
t.Fatalf("unknown flag result = %#v", result)
|
||||
}
|
||||
|
||||
result = Parse(reportingFlagTree(), []string{"run", "-qd", "--message"}, reportingFlagOptions())
|
||||
if result.Failure == nil || len(result.FlagOccurrences) != 2 || result.FlagOccurrences[0].Name != "quiet" || result.FlagOccurrences[1].Name != "debug" {
|
||||
t.Fatalf("missing value result = %#v", result)
|
||||
}
|
||||
|
||||
result = Parse(reportingFlagTree(), []string{"missing", "--quiet"}, reportingFlagOptions())
|
||||
if result.Failure == nil || len(result.FlagOccurrences) != 0 {
|
||||
t.Fatalf("malformed path result = %#v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFlagOccurrenceKeepsOptionLookingValueAndConsumesTerminator(t *testing.T) {
|
||||
result := Parse(reportingFlagTree(), []string{"run", "--message", "--quiet", "--", "--verbose"}, reportingFlagOptions())
|
||||
if result.Failure != nil {
|
||||
t.Fatalf("parse failed: %s", result.Failure.Message)
|
||||
}
|
||||
want := []commandmeta.FlagOccurrence{{Name: "message", Spelling: "--message", RawValue: "--quiet"}}
|
||||
if !reflect.DeepEqual(result.FlagOccurrences, want) {
|
||||
t.Fatalf("flag occurrences = %#v, want %#v", result.FlagOccurrences, want)
|
||||
}
|
||||
if !reflect.DeepEqual(result.Operands, []string{"--verbose"}) {
|
||||
t.Fatalf("operands = %#v", result.Operands)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCanonicalAndAliasShortGrammar(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
|
||||
@@ -80,7 +80,7 @@ func (session *Session) Parse(argv []string) commandmeta.Result {
|
||||
localFlags := make([]commandmeta.Flag, 0, len(selectedNode.Flags)+len(selectedNode.CoreFlags))
|
||||
localFlags = append(localFlags, selectedNode.Flags...)
|
||||
localFlags = append(localFlags, selectedNode.CoreFlags...)
|
||||
interpreted, spellings, interpretationFailure := interpretFlagTokens(remaining, localFlags, session.flagAliases)
|
||||
interpreted, spellings, occurrences, interpretationFailure := interpretFlagTokens(remaining, localFlags, session.flagAliases)
|
||||
for _, flag := range localFlags {
|
||||
if value := selected.Flags().Lookup(flag.Name); value != nil {
|
||||
value.Value.(*rawValue).queueSpellings(spellings[flag.Name])
|
||||
@@ -90,14 +90,17 @@ func (session *Session) Parse(argv []string) commandmeta.Result {
|
||||
helpFlag.Value.(*rawValue).queueSpellings(spellings["help"])
|
||||
}
|
||||
if err := selected.ParseFlags(interpreted); err != nil {
|
||||
result.FlagOccurrences = occurrences
|
||||
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.FlagOccurrences = occurrences
|
||||
result.Failure = interpretationFailure
|
||||
return result
|
||||
}
|
||||
result.FlagOccurrences = occurrences
|
||||
operands := selected.Flags().Args()
|
||||
|
||||
result.Operands = append([]string(nil), operands...)
|
||||
@@ -113,6 +116,15 @@ func (session *Session) Parse(argv []string) commandmeta.Result {
|
||||
if helpFlag := selected.Flags().Lookup("help"); helpFlag != nil {
|
||||
helpRequested, err := helpFlag.Value.(*rawValue).lastBool()
|
||||
if err != nil {
|
||||
for index, occurrence := range result.FlagOccurrences {
|
||||
if occurrence.Name != "help" {
|
||||
continue
|
||||
}
|
||||
if _, parseErr := strconv.ParseBool(occurrence.RawValue); parseErr != nil {
|
||||
result.FlagOccurrences = result.FlagOccurrences[:index+1]
|
||||
break
|
||||
}
|
||||
}
|
||||
return failure(result, err.Error())
|
||||
}
|
||||
result.HelpRequested = helpRequested
|
||||
|
||||
@@ -91,15 +91,41 @@ type Failure struct {
|
||||
Usage bool
|
||||
}
|
||||
|
||||
// FlagOccurrence records one parsed spelling and its unconverted value.
|
||||
type FlagOccurrence struct {
|
||||
Name string // canonical flag name
|
||||
Spelling string // entered long spelling or individual short spelling
|
||||
RawValue string // unconverted value collected by the parser
|
||||
}
|
||||
|
||||
// ReportingMode selects visibility for shared reporting messages.
|
||||
type ReportingMode uint8
|
||||
|
||||
const (
|
||||
ReportingModeNormal ReportingMode = iota
|
||||
ReportingModeQuiet
|
||||
ReportingModeVerbose
|
||||
ReportingModeDebug
|
||||
)
|
||||
|
||||
// ReportingSelection contains the resolved reporting options for one parse.
|
||||
type ReportingSelection struct {
|
||||
Mode ReportingMode
|
||||
Progress bool
|
||||
ProgressSet bool
|
||||
}
|
||||
|
||||
// Result contains the selected command and raw input collected by the adapter.
|
||||
type Result struct {
|
||||
Key Key
|
||||
Path []string
|
||||
HelpRequested bool
|
||||
FlagValues [][]string
|
||||
FlagSpellings [][]string
|
||||
Operands []string
|
||||
Failure *Failure
|
||||
Key Key
|
||||
Path []string
|
||||
HelpRequested bool
|
||||
FlagValues [][]string
|
||||
FlagSpellings [][]string
|
||||
FlagOccurrences []FlagOccurrence
|
||||
Reporting ReportingSelection
|
||||
Operands []string
|
||||
Failure *Failure
|
||||
}
|
||||
|
||||
// ParseOptions makes parser context and streams explicit.
|
||||
|
||||
Reference in New Issue
Block a user