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)
|
||||
}
|
||||
|
||||
func validateFields(f []bind.Field, conf Config) error {
|
||||
func validateFields(f map[string][]bind.Field, conf Config) error {
|
||||
hasConfigFromOption := hasConfigFromOption(conf)
|
||||
mf := make(map[string]bind.Field)
|
||||
for _, fi := range f {
|
||||
if ef, ok := mf[fi.Name()]; ok && !compatibleTypes(fi.Type(), ef.Type()) {
|
||||
return fmt.Errorf("duplicate fields with different types: %s", fi.Name())
|
||||
for name, ff := range f {
|
||||
var t []bind.FieldType
|
||||
for _, fi := range ff {
|
||||
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")
|
||||
}
|
||||
|
||||
mf[fi.Name()] = fi
|
||||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -2,31 +2,30 @@
|
||||
Generated with https://code.squareroundforest.org/arpio/docreflect
|
||||
*/
|
||||
|
||||
|
||||
package wand
|
||||
|
||||
import "code.squareroundforest.org/arpio/docreflect"
|
||||
|
||||
func init() {
|
||||
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.Exec", "\nfunc(o, stdin, args)")
|
||||
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.ClearCache", "")
|
||||
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.NoCache", "")
|
||||
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.MarkdownOptions", "")
|
||||
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.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.execTransparent", "\nfunc(command, 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.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.readExec", "\nfunc(o, stdin)")
|
||||
}
|
||||
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.Exec", "\nfunc(o, stdin, args)")
|
||||
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.ClearCache", "")
|
||||
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.NoCache", "")
|
||||
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.MarkdownOptions", "")
|
||||
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.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.execTransparent", "\nfunc(command, 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.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.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
|
||||
|
||||
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/notation v0.0.0-20250826181910-5140794b16b2
|
||||
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/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/go.mod h1:/3xQI36oJG8qLBxT2fSS61P5/+i1T64fTX9GHRh8XhA=
|
||||
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
|
||||
|
||||
import (
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
review if any symbols unused
|
||||
test:
|
||||
- nil return values
|
||||
- options in variadic
|
||||
@ -11,3 +10,6 @@ test:
|
||||
- implementation in pointer
|
||||
- implementation in 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 {
|
||||
return t
|
||||
return false
|
||||
}
|
||||
|
||||
if visited[t] {
|
||||
return t
|
||||
return true
|
||||
}
|
||||
|
||||
if visited == nil {
|
||||
@ -65,14 +65,40 @@ func unpackTypeChecked(visited map[reflect.Type]bool, t reflect.Type) reflect.Ty
|
||||
visited[t] = true
|
||||
switch t.Kind() {
|
||||
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:
|
||||
return t
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func circular(t reflect.Type) bool {
|
||||
return circularChecked(nil, t)
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -119,6 +145,7 @@ func isTime(t reflect.Type) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
t = unpackType(t)
|
||||
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]())
|
||||
}
|
||||
|
||||
func compatibleTypes(t ...bind.Scalar) bool {
|
||||
func compatibleTypes(t ...bind.FieldType) bool {
|
||||
if len(t) == 0 {
|
||||
return false
|
||||
}
|
||||
@ -186,8 +213,7 @@ func structParameters(f any) []reflect.Type {
|
||||
|
||||
func structFields(s reflect.Type) []bind.Field {
|
||||
s = unpackType(s)
|
||||
v := allocate(reflect.PointerTo(s))
|
||||
return bind.FieldValues(v.Interface())
|
||||
return bind.FieldsOf(s)
|
||||
}
|
||||
|
||||
func fields(f any) []bind.Field {
|
||||
@ -260,6 +286,10 @@ func bindable(t reflect.Type) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if circular(t) {
|
||||
return false
|
||||
}
|
||||
|
||||
t = unpackType(t)
|
||||
if isTime(t) {
|
||||
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)
|
||||
p := strings.Split(r.Name(), ".")
|
||||
n := p[len(p)-1]
|
||||
@ -302,30 +332,30 @@ func scalarTypeString(t bind.Scalar) string {
|
||||
return n
|
||||
}
|
||||
|
||||
func canScan(t bind.Scalar, v any) bool {
|
||||
func canScan(t bind.FieldType, v any) bool {
|
||||
switch t {
|
||||
case bind.Any:
|
||||
return true
|
||||
case bind.Bool:
|
||||
_, ok := bind.BindScalarCreate[bool](v)
|
||||
_, ok := bind.CreateAndBindScalar[bool](v)
|
||||
return ok
|
||||
case bind.Int:
|
||||
_, ok := bind.BindScalarCreate[int](v)
|
||||
case bind.Int, bind.Int8, bind.Int16, bind.Int32, bind.Int64:
|
||||
_, ok := bind.CreateAndBindScalar[int](v)
|
||||
return ok
|
||||
case bind.Uint:
|
||||
_, ok := bind.BindScalarCreate[uint](v)
|
||||
case bind.Uint, bind.Uint8, bind.Uint16, bind.Uint32, bind.Uint64:
|
||||
_, ok := bind.CreateAndBindScalar[uint](v)
|
||||
return ok
|
||||
case bind.Float:
|
||||
_, ok := bind.BindScalarCreate[float64](v)
|
||||
case bind.Float32, bind.Float64:
|
||||
_, ok := bind.CreateAndBindScalar[float64](v)
|
||||
return ok
|
||||
case bind.String:
|
||||
_, ok := bind.BindScalarCreate[string](v)
|
||||
_, ok := bind.CreateAndBindScalar[string](v)
|
||||
return ok
|
||||
case bind.Duration:
|
||||
_, ok := bind.BindScalarCreate[time.Duration](v)
|
||||
_, ok := bind.CreateAndBindScalar[time.Duration](v)
|
||||
return ok
|
||||
case bind.Time:
|
||||
_, ok := bind.BindScalarCreate[time.Time](v)
|
||||
_, ok := bind.CreateAndBindScalar[time.Time](v)
|
||||
return ok
|
||||
default:
|
||||
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
|
||||
for _, um := range unmapped {
|
||||
|
||||
@ -7,15 +7,21 @@ import (
|
||||
)
|
||||
|
||||
func TestReflect(t *testing.T) {
|
||||
t.Run("pack and unpack", func(t *testing.T) {
|
||||
f := func(a int) int { return a }
|
||||
t.Run("no need to pack", testExec(testCase{impl: f, command: "foo 42"}, "", "42"))
|
||||
g := func(a *int) int { return *a }
|
||||
t.Run("pointer", testExec(testCase{impl: g, command: "foo 42"}, "", "42"))
|
||||
h := func(a []int) int { return a[0] }
|
||||
t.Run("slice", testExec(testCase{impl: h, command: "foo 42"}, "", "42"))
|
||||
i := func(a *[]*[]int) int { return (*((*a)[0]))[0] }
|
||||
t.Run("pointer and slice", testExec(testCase{impl: i, command: "foo 42"}, "", "42"))
|
||||
t.Run("unpack", func(t *testing.T) {
|
||||
f := func(a []int) int { return a[0] }
|
||||
t.Run("slice", testExec(testCase{impl: f, command: "foo 42"}, "", "42"))
|
||||
ps := func(a *[]*[]int) int { return (*((*a)[0]))[0] }
|
||||
t.Run("pointer and slice", testExec(testCase{impl: ps, command: "foo 42"}, "", "42"))
|
||||
type s struct{Foo int; Bar *s}
|
||||
c := func(v s) int { return v.Foo }
|
||||
t.Run("circular type", testExec(testCase{impl: c, command: "foo --foo 42"}, "unsupported parameter type", ""))
|
||||
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) {
|
||||
@ -81,4 +87,16 @@ func TestReflect(t *testing.T) {
|
||||
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