fix(cli): Erstes „--“ schirmt Positionsargumente vollständig ab
Der Interspersed-Loop von Task 03 nahm nach jedem Positionsargument das Flag-Parsen wieder auf und hob damit die Stdlib-Terminator-Semantik von „--“ auf: Argumente, die für einen NACHFOLGENDEN Befehl gemeint waren, wurden als talk-Flags fehlinterpretiert (talk wrap -- foo --bar wäre an --bar als unbekanntem Flag gescheitert; auch talk -- „Text --flag“ brach). Jetzt trennt parseInterspersed argv am ersten allein stehenden „--“: Nur der Teil davor wird interspersed geparst, der Rest ist bedingungslos Positionsargument. Die Abnahme-Form „Flag nach dem Text“ ohne „--“ (talk "Text" --provider=x) bleibt unverändert — Regressionstests für beide Formen in parse_test.go.
This commit is contained in:
1 parent
a3424c36e5
commit
671a97ca71
2 files changed
+126
-1
No files matched your search
+28
-1
@@ -5,6 +5,7 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// Exit-Codes.
|
||||
@@ -151,12 +152,38 @@ func run(argv []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
||||
}
|
||||
|
||||
// parseInterspersed parst Flags, die überall in argv stehen dürfen. Die
|
||||
// Go-stdlib-flag stoppt beim ersten Nicht-Flag-AArgument; die CLI-Surface
|
||||
// Go-stdlib-flag stoppt beim ersten Nicht-Flag-Argument; die CLI-Surface
|
||||
// verlangt aber `talk "Text" --provider=x` (Abnahmekriterium Task 03)
|
||||
// genauso wie `talk --provider=x "Text"`. Trick: Parse über den Rest
|
||||
// wiederholen und jeweils das Positionsargument vor dem Weiterparsen
|
||||
// entnehmen. Fehler (unbekannte Flags, -h) kommen wie gewohnt vom FlagSet.
|
||||
//
|
||||
// Seit Task 07 schirmt das erste „--" (als eigenständiges Argument) alles
|
||||
// dahinter vollständig ab — die Stdlib-Terminator-Semantik, die der
|
||||
// Interspersed-Loop zuvor aushebelte: Er nahm nach jedem Positionsargument
|
||||
// das Parsen wieder auf und fraß damit Flags, die für den NACHFOLGENDEN
|
||||
// Befehl gemeint waren (`talk wrap -- foo --bar` wäre an --bar als
|
||||
// vermeintlichem talk-Flag gescheitert). Jetzt wird argv am ersten „--"
|
||||
// getrennt: Nur der Teil davor wird interspersed geparst, der Rest ist
|
||||
// bedingungslos Positionsargument — für say (`talk -- "Text --flag"`)
|
||||
// genauso wie für wrap (`talk wrap -- foo --bar`). Die Abnahme-Form
|
||||
// `talk "Text" --provider=x` (Flag nach dem Text, ohne „--") ist davon
|
||||
// unberührt. Ein „--" als Flag-Wert (unmittelbar nach --voice) trifft
|
||||
// diese Trennung nicht, weil nur ein allein stehendes „--" trennt.
|
||||
func parseInterspersed(fs *flag.FlagSet, argv []string) ([]string, error) {
|
||||
if i := slices.Index(argv, "--"); i >= 0 {
|
||||
positional, err := parseInterspersedLoop(fs, argv[:i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(positional, argv[i+1:]...), nil
|
||||
}
|
||||
return parseInterspersedLoop(fs, argv)
|
||||
}
|
||||
|
||||
// parseInterspersedLoop ist der Interspersed-Durchlauf — argv enthält kein
|
||||
// allein stehendes „--" mehr (parseInterspersed hat davor getrennt).
|
||||
func parseInterspersedLoop(fs *flag.FlagSet, argv []string) ([]string, error) {
|
||||
var positional []string
|
||||
rest := argv
|
||||
for {
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestParseInterspersedDoubleDash (Regression, Task 07): Das erste allein
|
||||
// stehende „--" schirmt alles Folgenden von der Flag-Auswertung ab — der
|
||||
// Kern der wrap-Aufrufform `talk wrap -- foo --bar`. Vorher nahm der
|
||||
// Interspersed-Loop nach jedem Positionsargument das Parsen wieder auf und
|
||||
// fraß damit Flags des nachfolgenden Befehls. Gleichzeitig bleibt die
|
||||
// Abnahme-Form „Flag nach dem Text" (ohne „--") intakt.
|
||||
func TestParseInterspersedDoubleDash(t *testing.T) {
|
||||
newWrapFS := func() (*flag.FlagSet, *sayOptions) {
|
||||
fs := flag.NewFlagSet("talk wrap", flag.ContinueOnError)
|
||||
fs.SetOutput(io.Discard)
|
||||
fs.Usage = func() {}
|
||||
return fs, addSayFlags(fs)
|
||||
}
|
||||
|
||||
t.Run("Doppelminus schirmt Befehls-Flags", func(t *testing.T) {
|
||||
fs, opts := newWrapFS()
|
||||
positional, err := parseInterspersed(fs, []string{"--", "foo", "--bar"})
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if strings.Join(positional, " ") != "foo --bar" {
|
||||
t.Errorf("Positionsargumente = %q, want %q", positional, []string{"foo", "--bar"})
|
||||
}
|
||||
if opts.wait {
|
||||
t.Error("--bar darf kein talk-Flag geworden sein (Shielding kaputt)")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("talk-Flags vor dem Doppelminus gelten", func(t *testing.T) {
|
||||
fs, opts := newWrapFS()
|
||||
positional, err := parseInterspersed(fs, []string{"--wait", "--", "sleep", "1"})
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if !opts.wait {
|
||||
t.Error("--wait vor dem -- muss als talk-Flag gelten")
|
||||
}
|
||||
if strings.Join(positional, " ") != "sleep 1" {
|
||||
t.Errorf("Positionsargumente = %q, want %q", positional, []string{"sleep", "1"})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Positional vor dem Doppelminus wird mitgeschleppt", func(t *testing.T) {
|
||||
fs, _ := newWrapFS()
|
||||
positional, err := parseInterspersed(fs, []string{"foo", "--", "--bar"})
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if strings.Join(positional, " ") != "foo --bar" {
|
||||
t.Errorf("Positionsargumente = %q, want %q", positional, []string{"foo", "--bar"})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("abgeschirmtes talk-Flag setzt den Flag nicht", func(t *testing.T) {
|
||||
fs, opts := newWrapFS()
|
||||
positional, err := parseInterspersed(fs, []string{"--", "--wait", "true"})
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if opts.wait {
|
||||
t.Error("--wait NACH dem -- gehört dem Befehl, nicht talk")
|
||||
}
|
||||
if strings.Join(positional, " ") != "--wait true" {
|
||||
t.Errorf("Positionsargumente = %q, want %q", positional, []string{"--wait", "true"})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Flag nach dem Text ohne Doppelminus bleibt erlaubt", func(t *testing.T) {
|
||||
fs, opts := newWrapFS()
|
||||
positional, err := parseInterspersed(fs, []string{"Text", "--lang=en"})
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v (Abnahme-Form Flag-nach-Text kaputt)", err)
|
||||
}
|
||||
if strings.Join(positional, " ") != "Text" || opts.lang != "en" {
|
||||
t.Errorf("Positionsargumente = %q, lang = %q — Flag-nach-Text muss weitergehen", positional, opts.lang)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Text nach dem Doppelminus kommt vollständig durch (say)", func(t *testing.T) {
|
||||
fs, _ := newWrapFS()
|
||||
positional, err := parseInterspersed(fs, []string{"--", "Bericht", "mit --flag", "fertig"})
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if strings.Join(positional, " ") != "Bericht mit --flag fertig" {
|
||||
t.Errorf("Positionsargumente = %q, want Wörter unverändert", positional)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user