Share flag consumption between parsing and completion

This commit is contained in:
2026-11-07 15:53:32 +00:00
parent 51a147a6e5
commit 02cd49d6b6
6 changed files with 153 additions and 114 deletions

View File

@@ -2,7 +2,6 @@ package cobra
import (
"fmt"
"strings"
commandmeta "go.maximumdirect.net/commandkit/internal/commandmeta"
)
@@ -20,78 +19,43 @@ func interpretFlagTokens(argv []string, flags []commandmeta.Flag, aliases []comm
interpreted = append(interpreted, argv[index:]...)
break
}
if kind == commandmeta.LongFlagToken {
original, value, attached := commandmeta.SplitLongFlag(token)
flag, ok := lookup.Find(original)
if !ok {
interpreted = append(interpreted, token)
return interpreted, spellings, occurrences, nil
}
spellings[flag.Name] = append(spellings[flag.Name], original)
if attached {
interpreted = append(interpreted, "--"+flag.Name+"="+value)
occurrences = append(occurrences, commandmeta.FlagOccurrence{Name: flag.Name, Spelling: original, RawValue: value})
index++
continue
}
if !flag.Boolean && index+1 < len(argv) {
value := argv[index+1]
interpreted = append(interpreted, "--"+flag.Name+"="+value)
occurrences = append(occurrences, commandmeta.FlagOccurrence{Name: flag.Name, Spelling: original, RawValue: value})
index += 2
continue
}
if !flag.Boolean {
return interpreted, spellings, occurrences, &commandmeta.Failure{Message: fmt.Sprintf("flag needs an argument: %s", original)}
}
occurrences = append(occurrences, commandmeta.FlagOccurrence{Name: flag.Name, Spelling: original, RawValue: "true"})
interpreted = append(interpreted, "--"+flag.Name)
index++
continue
}
if kind == commandmeta.ShortFlagToken {
if kind == commandmeta.LongFlagToken || kind == commandmeta.ShortFlagToken {
offset := 1
consumedNext := false
for offset := 1; offset < len(token); {
original := "-" + token[offset:offset+1]
flag, ok := lookup.Find(original)
if !ok {
interpreted = append(interpreted, "-"+token[offset:])
for {
fact := commandmeta.ConsumeFlag(lookup, token, offset)
if !fact.Found {
if kind == commandmeta.ShortFlagToken {
interpreted = append(interpreted, "-"+token[offset:])
} else {
interpreted = append(interpreted, token)
}
return interpreted, spellings, occurrences, nil
}
flag, original := fact.Flag, fact.Spelling
spellings[flag.Name] = append(spellings[flag.Name], original)
suffix := token[offset+1:]
if flag.Boolean {
if strings.HasPrefix(suffix, "=") {
value := suffix[1:]
interpreted = append(interpreted, "--"+flag.Name+"="+value)
occurrences = append(occurrences, commandmeta.FlagOccurrence{Name: flag.Name, Spelling: original, RawValue: value})
break
value := fact.Value
if fact.NeedsNext {
if index+1 >= len(argv) {
return interpreted, spellings, occurrences, &commandmeta.Failure{Message: fmt.Sprintf("flag needs an argument: %s", original)}
}
occurrences = append(occurrences, commandmeta.FlagOccurrence{Name: flag.Name, Spelling: original, RawValue: "true"})
interpreted = append(interpreted, "--"+flag.Name)
offset++
continue
}
if suffix != "" {
value := suffix
if strings.HasPrefix(value, "=") {
value = value[1:]
}
interpreted = append(interpreted, "--"+flag.Name+"="+value)
occurrences = append(occurrences, commandmeta.FlagOccurrence{Name: flag.Name, Spelling: original, RawValue: value})
break
}
if index+1 < len(argv) {
value := argv[index+1]
interpreted = append(interpreted, "--"+flag.Name+"="+value)
occurrences = append(occurrences, commandmeta.FlagOccurrence{Name: flag.Name, Spelling: original, RawValue: value})
index += 2
value = argv[index+1]
consumedNext = true
}
if !fact.Attached && !fact.NeedsNext {
value = "true"
interpreted = append(interpreted, "--"+flag.Name)
} else {
interpreted = append(interpreted, "--"+flag.Name+"="+value)
}
occurrences = append(occurrences, commandmeta.FlagOccurrence{Name: flag.Name, Spelling: original, RawValue: value})
if fact.NextOffset == 0 {
break
}
return interpreted, spellings, occurrences, &commandmeta.Failure{Message: fmt.Sprintf("flag needs an argument: %s", original)}
offset = fact.NextOffset
}
if !consumedNext {
index++
if consumedNext {
index++
}
continue

View File

@@ -65,3 +65,47 @@ func (lookup FlagLookup) Find(spelling string) (Flag, bool) {
}
return flag, ok
}
// FlagConsumption describes one flag within an option token. NextOffset is zero
// when the token is exhausted; otherwise it identifies the next short spelling.
type FlagConsumption struct {
Flag Flag
Spelling string
Found bool
Value string
Attached bool
NeedsNext bool
NextOffset int
}
// ConsumeFlag reads a long option or a short option at offset in a cluster.
func ConsumeFlag(lookup FlagLookup, token string, offset int) FlagConsumption {
var result FlagConsumption
if ClassifyFlagToken(token) == LongFlagToken {
result.Spelling, result.Value, result.Attached = SplitLongFlag(token)
result.Flag, result.Found = lookup.Find(result.Spelling)
result.NeedsNext = result.Found && !result.Flag.Boolean && !result.Attached
return result
}
result.Spelling = "-" + token[offset:offset+1]
result.Flag, result.Found = lookup.Find(result.Spelling)
if !result.Found {
return result
}
suffix := token[offset+1:]
if result.Flag.Boolean {
if strings.HasPrefix(suffix, "=") {
result.Attached, result.Value = true, suffix[1:]
} else if suffix != "" {
result.NextOffset = offset + 1
}
return result
}
if suffix == "" {
result.NeedsNext = true
return result
}
result.Attached = true
result.Value = strings.TrimPrefix(suffix, "=")
return result
}