update bind dependency version
This commit is contained in:
parent
221de5c509
commit
c05cfc7656
20
command.go
20
command.go
@ -19,19 +19,21 @@ func wrap(impl any) Cmd {
|
|||||||
return Command("", impl)
|
return Command("", impl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateFields(f []bind.Field, conf Config) error {
|
func validateFields(f map[string][]bind.Field, conf Config) error {
|
||||||
hasConfigFromOption := hasConfigFromOption(conf)
|
hasConfigFromOption := hasConfigFromOption(conf)
|
||||||
mf := make(map[string]bind.Field)
|
for name, ff := range f {
|
||||||
for _, fi := range f {
|
var t []bind.FieldType
|
||||||
if ef, ok := mf[fi.Name()]; ok && !compatibleTypes(fi.Type(), ef.Type()) {
|
for _, fi := range ff {
|
||||||
return fmt.Errorf("duplicate fields with different types: %s", fi.Name())
|
t = append(t, fi.Type())
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasConfigFromOption && fi.Name() == "config" {
|
if !compatibleTypes(t...) {
|
||||||
|
return fmt.Errorf("duplicate fields with different types: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasConfigFromOption && name == "config" {
|
||||||
return errors.New("option reserved for config file shadowed by struct field")
|
return errors.New("option reserved for config file shadowed by struct field")
|
||||||
}
|
}
|
||||||
|
|
||||||
mf[fi.Name()] = fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -98,7 +100,7 @@ func validateImpl(cmd Cmd, conf Config) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f := fields(cmd.impl)
|
f := mapFields(cmd.impl)
|
||||||
if err := validateFields(f, conf); err != nil {
|
if err := validateFields(f, conf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,31 +2,30 @@
|
|||||||
Generated with https://code.squareroundforest.org/arpio/docreflect
|
Generated with https://code.squareroundforest.org/arpio/docreflect
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package wand
|
package wand
|
||||||
|
|
||||||
import "code.squareroundforest.org/arpio/docreflect"
|
import "code.squareroundforest.org/arpio/docreflect"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools", "")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools", "")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Docreflect", "\nfunc(out, packageName, gopaths)")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Docreflect", "\nfunc(out, packageName, gopaths)")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Exec", "\nfunc(o, stdin, args)")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Exec", "\nfunc(o, stdin, args)")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions", "")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions", "")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.CacheDir", "")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.CacheDir", "")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.ClearCache", "")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.ClearCache", "")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.Import", "")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.Import", "")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.InlineImport", "")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.InlineImport", "")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.NoCache", "")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.NoCache", "")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Man", "\nfunc(out, commandDir)")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Man", "\nfunc(out, commandDir)")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Markdown", "\nfunc(out, o, commandDir)")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Markdown", "\nfunc(out, o, commandDir)")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.MarkdownOptions", "")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.MarkdownOptions", "")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.MarkdownOptions.Level", "")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.MarkdownOptions.Level", "")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.commandReader", "\nfunc(in)")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.commandReader", "\nfunc(in)")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execCommandDir", "\nfunc(out, commandDir, env)")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execCommandDir", "\nfunc(out, commandDir, env)")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execInternal", "\nfunc(command, args)")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execInternal", "\nfunc(command, args)")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execTransparent", "\nfunc(command, args)")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execTransparent", "\nfunc(command, args)")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execWand", "\nfunc(o, args)")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execWand", "\nfunc(o, args)")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execc", "\nfunc(stdin, stdout, stderr, command, args, env)")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execc", "\nfunc(stdin, stdout, stderr, command, args, env)")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.hash", "\nfunc(expression, imports, inlineImports)")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.hash", "\nfunc(expression, imports, inlineImports)")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.printGoFile", "\nfunc(fn, expression, imports, inlineImports)")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.printGoFile", "\nfunc(fn, expression, imports, inlineImports)")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.readExec", "\nfunc(o, stdin)")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.readExec", "\nfunc(o, stdin)")
|
||||||
}
|
}
|
||||||
5
docs.go
5
docs.go
@ -1,5 +0,0 @@
|
|||||||
/*
|
|
||||||
Wand provides utilities for constructing command line applications from functions, with automatic parameter
|
|
||||||
binding from command line arguments, environment variables and configuration files.
|
|
||||||
*/
|
|
||||||
package wand
|
|
||||||
2
go.mod
2
go.mod
@ -3,7 +3,7 @@ module code.squareroundforest.org/arpio/wand
|
|||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
code.squareroundforest.org/arpio/bind v0.0.0-20250901011104-bcadfd8b71fc
|
code.squareroundforest.org/arpio/bind v0.0.0-20250903223305-8683d8ba4074
|
||||||
code.squareroundforest.org/arpio/docreflect v0.0.0-20250831183400-d26ecc663a30
|
code.squareroundforest.org/arpio/docreflect v0.0.0-20250831183400-d26ecc663a30
|
||||||
code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2
|
code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2
|
||||||
code.squareroundforest.org/arpio/treerack v0.0.0-20250820014405-1d956dcc6610
|
code.squareroundforest.org/arpio/treerack v0.0.0-20250820014405-1d956dcc6610
|
||||||
|
|||||||
2
go.sum
2
go.sum
@ -1,5 +1,7 @@
|
|||||||
code.squareroundforest.org/arpio/bind v0.0.0-20250901011104-bcadfd8b71fc h1:nu5YXVLDrRzN9Ea5agXmhxFILyVAPyoED25ksTYC9ws=
|
code.squareroundforest.org/arpio/bind v0.0.0-20250901011104-bcadfd8b71fc h1:nu5YXVLDrRzN9Ea5agXmhxFILyVAPyoED25ksTYC9ws=
|
||||||
code.squareroundforest.org/arpio/bind v0.0.0-20250901011104-bcadfd8b71fc/go.mod h1:tTCmCwFABKNm3PO0Dclsp4zWhNQFTfg9+uSrgoarZFI=
|
code.squareroundforest.org/arpio/bind v0.0.0-20250901011104-bcadfd8b71fc/go.mod h1:tTCmCwFABKNm3PO0Dclsp4zWhNQFTfg9+uSrgoarZFI=
|
||||||
|
code.squareroundforest.org/arpio/bind v0.0.0-20250903223305-8683d8ba4074 h1:OTzn0dMou+6m2rw70g7fIylQLHUTu75noAX3lbCYMqw=
|
||||||
|
code.squareroundforest.org/arpio/bind v0.0.0-20250903223305-8683d8ba4074/go.mod h1:tTCmCwFABKNm3PO0Dclsp4zWhNQFTfg9+uSrgoarZFI=
|
||||||
code.squareroundforest.org/arpio/docreflect v0.0.0-20250831183400-d26ecc663a30 h1:QUCgxUEA5/ng7GwRnzb/WezmFQXSHXl48GdLJc0KC5k=
|
code.squareroundforest.org/arpio/docreflect v0.0.0-20250831183400-d26ecc663a30 h1:QUCgxUEA5/ng7GwRnzb/WezmFQXSHXl48GdLJc0KC5k=
|
||||||
code.squareroundforest.org/arpio/docreflect v0.0.0-20250831183400-d26ecc663a30/go.mod h1:/3xQI36oJG8qLBxT2fSS61P5/+i1T64fTX9GHRh8XhA=
|
code.squareroundforest.org/arpio/docreflect v0.0.0-20250831183400-d26ecc663a30/go.mod h1:/3xQI36oJG8qLBxT2fSS61P5/+i1T64fTX9GHRh8XhA=
|
||||||
code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2 h1:S4mjQHL70CuzFg1AGkr0o0d+4M+ZWM0sbnlYq6f0b3I=
|
code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2 h1:S4mjQHL70CuzFg1AGkr0o0d+4M+ZWM0sbnlYq6f0b3I=
|
||||||
|
|||||||
2
lib.go
2
lib.go
@ -1,3 +1,5 @@
|
|||||||
|
// Wand provides utilities for constructing command line applications from functions, with automatic parameter
|
||||||
|
// binding from command line arguments, environment variables and configuration files.
|
||||||
package wand
|
package wand
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
review if any symbols unused
|
|
||||||
test:
|
test:
|
||||||
- nil return values
|
- nil return values
|
||||||
- options in variadic
|
- options in variadic
|
||||||
@ -11,3 +10,6 @@ test:
|
|||||||
- implementation in pointer
|
- implementation in pointer
|
||||||
- implementation in slice => not accepted
|
- implementation in slice => not accepted
|
||||||
- implementation in pointer and slice => not accepted
|
- implementation in pointer and slice => not accepted
|
||||||
|
- plurality
|
||||||
|
- env parsing
|
||||||
|
- plurality in input validation
|
||||||
|
|||||||
74
reflect.go
74
reflect.go
@ -49,13 +49,13 @@ func or[T any](p ...func(T) bool) func(T) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func unpackTypeChecked(visited map[reflect.Type]bool, t reflect.Type) reflect.Type {
|
func circularChecked(visited map[reflect.Type]bool, t reflect.Type) bool {
|
||||||
if t == nil {
|
if t == nil {
|
||||||
return t
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if visited[t] {
|
if visited[t] {
|
||||||
return t
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if visited == nil {
|
if visited == nil {
|
||||||
@ -65,14 +65,40 @@ func unpackTypeChecked(visited map[reflect.Type]bool, t reflect.Type) reflect.Ty
|
|||||||
visited[t] = true
|
visited[t] = true
|
||||||
switch t.Kind() {
|
switch t.Kind() {
|
||||||
case reflect.Pointer, reflect.Slice:
|
case reflect.Pointer, reflect.Slice:
|
||||||
return unpackTypeChecked(visited, t.Elem())
|
return circularChecked(visited, t.Elem())
|
||||||
|
case reflect.Struct:
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
svisited := make(map[reflect.Type]bool)
|
||||||
|
for t := range visited {
|
||||||
|
svisited[t] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if circularChecked(svisited, t.Field(i).Type) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
default:
|
default:
|
||||||
return t
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func circular(t reflect.Type) bool {
|
||||||
|
return circularChecked(nil, t)
|
||||||
|
}
|
||||||
|
|
||||||
func unpackType(t reflect.Type) reflect.Type {
|
func unpackType(t reflect.Type) reflect.Type {
|
||||||
return unpackTypeChecked(nil, t)
|
if t == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Pointer, reflect.Slice:
|
||||||
|
return unpackType(t.Elem())
|
||||||
|
default:
|
||||||
|
return t
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func unpackValueChecked(visited map[uintptr]bool, v reflect.Value) reflect.Value {
|
func unpackValueChecked(visited map[uintptr]bool, v reflect.Value) reflect.Value {
|
||||||
@ -119,6 +145,7 @@ func isTime(t reflect.Type) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t = unpackType(t)
|
||||||
return t.ConvertibleTo(reflect.TypeFor[time.Time]())
|
return t.ConvertibleTo(reflect.TypeFor[time.Time]())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +174,7 @@ func isWriter(t reflect.Type) bool {
|
|||||||
return t.NumMethod() == 1 && t.Implements(reflect.TypeFor[io.Writer]())
|
return t.NumMethod() == 1 && t.Implements(reflect.TypeFor[io.Writer]())
|
||||||
}
|
}
|
||||||
|
|
||||||
func compatibleTypes(t ...bind.Scalar) bool {
|
func compatibleTypes(t ...bind.FieldType) bool {
|
||||||
if len(t) == 0 {
|
if len(t) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -186,8 +213,7 @@ func structParameters(f any) []reflect.Type {
|
|||||||
|
|
||||||
func structFields(s reflect.Type) []bind.Field {
|
func structFields(s reflect.Type) []bind.Field {
|
||||||
s = unpackType(s)
|
s = unpackType(s)
|
||||||
v := allocate(reflect.PointerTo(s))
|
return bind.FieldsOf(s)
|
||||||
return bind.FieldValues(v.Interface())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func fields(f any) []bind.Field {
|
func fields(f any) []bind.Field {
|
||||||
@ -260,6 +286,10 @@ func bindable(t reflect.Type) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if circular(t) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
t = unpackType(t)
|
t = unpackType(t)
|
||||||
if isTime(t) {
|
if isTime(t) {
|
||||||
return true
|
return true
|
||||||
@ -294,7 +324,7 @@ func bindable(t reflect.Type) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func scalarTypeString(t bind.Scalar) string {
|
func scalarTypeString(t bind.FieldType) string {
|
||||||
r := reflect.TypeOf(t)
|
r := reflect.TypeOf(t)
|
||||||
p := strings.Split(r.Name(), ".")
|
p := strings.Split(r.Name(), ".")
|
||||||
n := p[len(p)-1]
|
n := p[len(p)-1]
|
||||||
@ -302,30 +332,30 @@ func scalarTypeString(t bind.Scalar) string {
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
func canScan(t bind.Scalar, v any) bool {
|
func canScan(t bind.FieldType, v any) bool {
|
||||||
switch t {
|
switch t {
|
||||||
case bind.Any:
|
case bind.Any:
|
||||||
return true
|
return true
|
||||||
case bind.Bool:
|
case bind.Bool:
|
||||||
_, ok := bind.BindScalarCreate[bool](v)
|
_, ok := bind.CreateAndBindScalar[bool](v)
|
||||||
return ok
|
return ok
|
||||||
case bind.Int:
|
case bind.Int, bind.Int8, bind.Int16, bind.Int32, bind.Int64:
|
||||||
_, ok := bind.BindScalarCreate[int](v)
|
_, ok := bind.CreateAndBindScalar[int](v)
|
||||||
return ok
|
return ok
|
||||||
case bind.Uint:
|
case bind.Uint, bind.Uint8, bind.Uint16, bind.Uint32, bind.Uint64:
|
||||||
_, ok := bind.BindScalarCreate[uint](v)
|
_, ok := bind.CreateAndBindScalar[uint](v)
|
||||||
return ok
|
return ok
|
||||||
case bind.Float:
|
case bind.Float32, bind.Float64:
|
||||||
_, ok := bind.BindScalarCreate[float64](v)
|
_, ok := bind.CreateAndBindScalar[float64](v)
|
||||||
return ok
|
return ok
|
||||||
case bind.String:
|
case bind.String:
|
||||||
_, ok := bind.BindScalarCreate[string](v)
|
_, ok := bind.CreateAndBindScalar[string](v)
|
||||||
return ok
|
return ok
|
||||||
case bind.Duration:
|
case bind.Duration:
|
||||||
_, ok := bind.BindScalarCreate[time.Duration](v)
|
_, ok := bind.CreateAndBindScalar[time.Duration](v)
|
||||||
return ok
|
return ok
|
||||||
case bind.Time:
|
case bind.Time:
|
||||||
_, ok := bind.BindScalarCreate[time.Time](v)
|
_, ok := bind.CreateAndBindScalar[time.Time](v)
|
||||||
return ok
|
return ok
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
@ -372,7 +402,7 @@ func bindFields(receiver reflect.Value, values map[string][]any) []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unmapped := bind.BindFields(receiver.Interface(), f...)
|
unmapped := bind.Bind(receiver.Interface(), f...)
|
||||||
|
|
||||||
var names []string
|
var names []string
|
||||||
for _, um := range unmapped {
|
for _, um := range unmapped {
|
||||||
|
|||||||
@ -7,15 +7,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestReflect(t *testing.T) {
|
func TestReflect(t *testing.T) {
|
||||||
t.Run("pack and unpack", func(t *testing.T) {
|
t.Run("unpack", func(t *testing.T) {
|
||||||
f := func(a int) int { return a }
|
f := func(a []int) int { return a[0] }
|
||||||
t.Run("no need to pack", testExec(testCase{impl: f, command: "foo 42"}, "", "42"))
|
t.Run("slice", testExec(testCase{impl: f, command: "foo 42"}, "", "42"))
|
||||||
g := func(a *int) int { return *a }
|
ps := func(a *[]*[]int) int { return (*((*a)[0]))[0] }
|
||||||
t.Run("pointer", testExec(testCase{impl: g, command: "foo 42"}, "", "42"))
|
t.Run("pointer and slice", testExec(testCase{impl: ps, command: "foo 42"}, "", "42"))
|
||||||
h := func(a []int) int { return a[0] }
|
type s struct{Foo int; Bar *s}
|
||||||
t.Run("slice", testExec(testCase{impl: h, command: "foo 42"}, "", "42"))
|
c := func(v s) int { return v.Foo }
|
||||||
i := func(a *[]*[]int) int { return (*((*a)[0]))[0] }
|
t.Run("circular type", testExec(testCase{impl: c, command: "foo --foo 42"}, "unsupported parameter type", ""))
|
||||||
t.Run("pointer and slice", testExec(testCase{impl: i, command: "foo 42"}, "", "42"))
|
fp := func(a int) int { return a }
|
||||||
|
t.Run("function pointer", testExec(testCase{impl: &fp, command: "foo 42"}, "", "42"))
|
||||||
|
var p any
|
||||||
|
p = &p
|
||||||
|
t.Run("circular reference", testExec(testCase{impl: p, command: "foo"}, "must be a function or a pointer to a function", ""))
|
||||||
|
var i any
|
||||||
|
t.Run("nil interface", testExec(testCase{impl: &i, command: "foo"}, "must be a function or a pointer to a function", ""))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("io params", func(t *testing.T) {
|
t.Run("io params", func(t *testing.T) {
|
||||||
@ -81,4 +87,16 @@ func TestReflect(t *testing.T) {
|
|||||||
t.Run("unscannable", testExec(testCase{impl: g, command: "foo bar"}, "unsupported parameter type", ""))
|
t.Run("unscannable", testExec(testCase{impl: g, command: "foo bar"}, "unsupported parameter type", ""))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("compatible types", func(t *testing.T) {
|
||||||
|
type s0 struct{FooBar int; Foo struct {Bar string}}
|
||||||
|
type s1 struct{FooBar int; Foo struct {Bar int}}
|
||||||
|
f := func(a s0) int { return a.FooBar + len(a.Foo.Bar) }
|
||||||
|
g := func(a s1) int { return a.FooBar + a.Foo.Bar }
|
||||||
|
t.Run("incompatible", testExec(testCase{impl: f, command: "foo --foo-bar 42"}, "duplicate fields with different types", ""))
|
||||||
|
t.Run("compatible", testExec(testCase{impl: g, command: "foo --foo-bar 42"}, "", "84"))
|
||||||
|
type s2 struct{FooBar any; Foo struct {Bar int}}
|
||||||
|
h := func(a s2) int { return len(a.FooBar.(string)) + a.Foo.Bar }
|
||||||
|
t.Run("any interface", testExec(testCase{impl: h, command: "foo --foo-bar 42"}, "", "44"))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user