Unify typed binding preparation

This commit is contained in:
2026-10-11 01:18:07 +00:00
parent de9bfdcc4c
commit f63b096525
5 changed files with 130 additions and 65 deletions

View File

@@ -104,8 +104,7 @@ func TestBoolFlagRejectsExplicitEmptyValue(t *testing.T) {
func TestRepeatFlagUsesFreshDefaultsAndWholeOccurrenceValues(t *testing.T) {
defaults := []string{"base"}
var captured Supplied[[]string]
flag := RepeatFlag(FlagMeta{Name: "tag"}, defaults, func(value string) (string, error) { return value, nil }, func(_ *bindingInput, value Supplied[[]string]) { captured = value })
flag := RepeatFlag(FlagMeta{Name: "tag"}, defaults, func(value string) (string, error) { return value, nil }, func(input *bindingInput, value Supplied[[]string]) { input.tags = value })
defaults[0] = "changed after declaration"
applyDefault, err := flag.binding.prepare(nil)
@@ -113,25 +112,25 @@ func TestRepeatFlagUsesFreshDefaultsAndWholeOccurrenceValues(t *testing.T) {
t.Fatal(err)
}
first := &bindingInput{}
applyDefault(first)
applyDefault.apply(first)
second := &bindingInput{}
applyDefault(second)
if !reflect.DeepEqual(captured.Value, []string{"base"}) || captured.Set {
t.Fatalf("omitted repeat value = %#v, want copied default with Set=false", captured)
applyDefault.apply(second)
if !reflect.DeepEqual(first.tags.Value, []string{"base"}) || first.tags.Set {
t.Fatalf("omitted repeat value = %#v, want copied default with Set=false", first.tags)
}
captured.Value[0] = "changed one input"
applyDefault(second)
if !reflect.DeepEqual(captured.Value, []string{"base"}) {
t.Fatalf("repeat default collection was reused: %#v", captured.Value)
first.tags.Value[0] = "changed one input"
if !reflect.DeepEqual(second.tags.Value, []string{"base"}) {
t.Fatalf("repeat default collection was reused: %#v", second.tags.Value)
}
applyValues, err := flag.binding.prepare([]string{"a,b", "c"})
if err != nil {
t.Fatal(err)
}
applyValues(&bindingInput{})
if !reflect.DeepEqual(captured.Value, []string{"a,b", "c"}) || !captured.Set {
t.Fatalf("supplied repeat values = %#v, want two ordered occurrences", captured)
third := &bindingInput{}
applyValues.apply(third)
if !reflect.DeepEqual(third.tags.Value, []string{"a,b", "c"}) || !third.tags.Set {
t.Fatalf("supplied repeat values = %#v, want two ordered occurrences", third.tags)
}
}
@@ -149,6 +148,46 @@ func TestSingletonParsesEveryOccurrenceAndPreservesCallerCause(t *testing.T) {
}
}
func TestPreparedScalarConvertsEveryOccurrenceOnceBeforeInputCreation(t *testing.T) {
parseCalls := 0
var received Supplied[string]
flag := ValueFlag(FlagMeta{Name: "value"}, (*string)(nil), func(value string) (string, error) {
parseCalls++
return value, nil
}, func(input *bindingInput, value Supplied[string]) { input.message = value })
prepared, err := flag.binding.prepare([]string{"earlier", "last"})
if err != nil {
t.Fatal(err)
}
if parseCalls != 2 || prepared.identity != flag.binding.identity || !prepared.supplied {
t.Fatalf("prepared scalar = %#v, parse calls = %d", prepared, parseCalls)
}
copy := flag
lookalike := ValueFlag(FlagMeta{Name: "value"}, (*string)(nil), func(value string) (string, error) { return value, nil }, func(*bindingInput, Supplied[string]) {})
if copy.binding.identity != flag.binding.identity || lookalike.binding.identity == flag.binding.identity {
t.Fatal("binding identity was not preserved by copying or distinct across constructors")
}
input := &bindingInput{}
prepared.apply(input)
received = input.message
if parseCalls != 2 || received.Value != "last" || !received.Set {
t.Fatalf("applied value = %#v, parse calls = %d", received, parseCalls)
}
}
func TestInvalidBindingConstructionIsReportedAtInstallation(t *testing.T) {
badFlag := ValueFlag[struct{}, string](FlagMeta{Name: "broken"}, nil, nil, func(*struct{}, Supplied[string]) {})
badArgument := RepeatArgument[struct{}, string](ArgumentMeta{Name: "items"}, 2, 1, func(value string) (string, error) { return value, nil }, func(*struct{}, Supplied[[]string]) {})
command := NewCommand(CommandSpec[struct{}]{Name: "run", Flags: []Flag[struct{}]{badFlag}, Arguments: []Argument[struct{}]{badArgument}})
app, err := New(Identity{Name: "example"}, command)
if err == nil {
t.Fatal("installation accepted nil parser wiring and invalid argument cardinality")
}
if app != nil {
t.Fatal("failed installation returned a usable app")
}
}
func TestRepeatArgumentAllocationReservesLaterMinimums(t *testing.T) {
type operandsInput struct {
prefix Supplied[[]string]