Fix enum's from envars not validating (fixes #107).
Also added a mapper for `*os.File`.
This commit is contained in:
@@ -382,11 +382,21 @@ Slices and maps treat type tags specially. For slices, the `type:""` tag
|
|||||||
specifies the element type. For maps, the tag has the format
|
specifies the element type. For maps, the tag has the format
|
||||||
`tag:"[<key>]:[<value>]"` where either may be omitted.
|
`tag:"[<key>]:[<value>]"` where either may be omitted.
|
||||||
|
|
||||||
|
## Supported field types
|
||||||
|
|
||||||
|
|
||||||
## Custom decoders (mappers)
|
## Custom decoders (mappers)
|
||||||
|
|
||||||
|
|
||||||
Any field implementing `encoding.TextUnmarshaler` or `json.Unmarshaler` will use those interfaces
|
Any field implementing `encoding.TextUnmarshaler` or `json.Unmarshaler` will use those interfaces
|
||||||
for decoding values.
|
for decoding values. Kong also includes builtin support for many common Go types:
|
||||||
|
|
||||||
|
| Type | Description
|
||||||
|
|---------------------|--------------------------------------------
|
||||||
|
| `time.Duration` | Populated using `time.ParseDuration()`.
|
||||||
|
| `time.Time` | Populated using `time.Parse()`. Format defaults to RFC3339 but can be overridden with the `format:"X"` tag.
|
||||||
|
| `*os.File` | Path to a file that will be opened, or `-` for `os.Stdin`. File must be closed by the user.
|
||||||
|
| `*url.URL` | Populated with `url.Parse()`.
|
||||||
|
|
||||||
For more fine-grained control, if a field implements the
|
For more fine-grained control, if a field implements the
|
||||||
[MapperValue](https://godoc.org/github.com/alecthomas/kong#MapperValue)
|
[MapperValue](https://godoc.org/github.com/alecthomas/kong#MapperValue)
|
||||||
|
|||||||
+3
-1
@@ -2,6 +2,7 @@ package kong
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -136,7 +137,8 @@ func (c *Context) Empty() bool {
|
|||||||
func (c *Context) Validate() error { // nolint: gocyclo
|
func (c *Context) Validate() error { // nolint: gocyclo
|
||||||
err := Visit(c.Model, func(node Visitable, next Next) error {
|
err := Visit(c.Model, func(node Visitable, next Next) error {
|
||||||
if value, ok := node.(*Value); ok {
|
if value, ok := node.(*Value); ok {
|
||||||
if value.Enum != "" && (!value.Required || value.Default != "") {
|
_, ok := os.LookupEnv(value.Tag.Env)
|
||||||
|
if value.Enum != "" && (!value.Required || value.Default != "" || (value.Tag.Env != "" && ok)) {
|
||||||
if err := checkEnum(value, value.Target); err != nil {
|
if err := checkEnum(value, value.Target); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package kong
|
package kong
|
||||||
|
|
||||||
// BeforeResolve is a documentation-only interface describing hooks that run before values are set.
|
// BeforeResolve is a documentation-only interface describing hooks that run before resolvers are applied.
|
||||||
type BeforeResolve interface {
|
type BeforeResolve interface {
|
||||||
// This is not the correct signature - see README for details.
|
// This is not the correct signature - see README for details.
|
||||||
BeforeResolve(args ...interface{}) error
|
BeforeResolve(args ...interface{}) error
|
||||||
|
|||||||
+2
-2
@@ -743,7 +743,7 @@ func TestEnvarEnumValidated(t *testing.T) {
|
|||||||
})
|
})
|
||||||
defer restore()
|
defer restore()
|
||||||
var cli struct {
|
var cli struct {
|
||||||
Flag string `env:"FLAG" enum:"valid" default:"valid"`
|
Flag string `env:"FLAG" required:"" enum:"valid"`
|
||||||
}
|
}
|
||||||
p := mustNew(t, &cli)
|
p := mustNew(t, &cli)
|
||||||
_, err := p.Parse(nil)
|
_, err := p.Parse(nil)
|
||||||
@@ -798,7 +798,7 @@ func TestIssue40EnumAcrossCommands(t *testing.T) {
|
|||||||
OneArg string `arg:"" required:""`
|
OneArg string `arg:"" required:""`
|
||||||
} `cmd:""`
|
} `cmd:""`
|
||||||
Two struct {
|
Two struct {
|
||||||
TwoArg string `arg:"" enum:"a,b,c" required:""`
|
TwoArg string `arg:"" enum:"a,b,c" required:"" env:"FOO"`
|
||||||
} `cmd:""`
|
} `cmd:""`
|
||||||
Three struct {
|
Three struct {
|
||||||
ThreeArg string `arg:"" optional:"" default:"a" enum:"a,b,c"`
|
ThreeArg string `arg:"" optional:"" default:"a" enum:"a,b,c"`
|
||||||
|
|||||||
@@ -231,8 +231,8 @@ func (r *Registry) RegisterDefaults() *Registry {
|
|||||||
RegisterKind(reflect.Int16, intDecoder(16)).
|
RegisterKind(reflect.Int16, intDecoder(16)).
|
||||||
RegisterKind(reflect.Int32, intDecoder(32)).
|
RegisterKind(reflect.Int32, intDecoder(32)).
|
||||||
RegisterKind(reflect.Int64, intDecoder(64)).
|
RegisterKind(reflect.Int64, intDecoder(64)).
|
||||||
RegisterKind(reflect.Uint, uintDecoder(64)).
|
RegisterKind(reflect.Uint, uintDecoder(bits.UintSize)).
|
||||||
RegisterKind(reflect.Uint8, uintDecoder(bits.UintSize)).
|
RegisterKind(reflect.Uint8, uintDecoder(8)).
|
||||||
RegisterKind(reflect.Uint16, uintDecoder(16)).
|
RegisterKind(reflect.Uint16, uintDecoder(16)).
|
||||||
RegisterKind(reflect.Uint32, uintDecoder(32)).
|
RegisterKind(reflect.Uint32, uintDecoder(32)).
|
||||||
RegisterKind(reflect.Uint64, uintDecoder(64)).
|
RegisterKind(reflect.Uint64, uintDecoder(64)).
|
||||||
@@ -248,6 +248,7 @@ func (r *Registry) RegisterDefaults() *Registry {
|
|||||||
RegisterType(reflect.TypeOf(time.Time{}), timeDecoder()).
|
RegisterType(reflect.TypeOf(time.Time{}), timeDecoder()).
|
||||||
RegisterType(reflect.TypeOf(time.Duration(0)), durationDecoder()).
|
RegisterType(reflect.TypeOf(time.Duration(0)), durationDecoder()).
|
||||||
RegisterType(reflect.TypeOf(&url.URL{}), urlMapper()).
|
RegisterType(reflect.TypeOf(&url.URL{}), urlMapper()).
|
||||||
|
RegisterType(reflect.TypeOf(&os.File{}), fileMapper(r)).
|
||||||
RegisterName("path", pathMapper(r)).
|
RegisterName("path", pathMapper(r)).
|
||||||
RegisterName("existingfile", existingFileMapper(r)).
|
RegisterName("existingfile", existingFileMapper(r)).
|
||||||
RegisterName("existingdir", existingDirMapper(r)).
|
RegisterName("existingdir", existingDirMapper(r)).
|
||||||
@@ -541,6 +542,31 @@ func pathMapper(r *Registry) MapperFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fileMapper(r *Registry) MapperFunc {
|
||||||
|
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||||
|
if target.Kind() == reflect.Slice {
|
||||||
|
return sliceDecoder(r)(ctx, target)
|
||||||
|
}
|
||||||
|
var path string
|
||||||
|
err := ctx.Scan.PopValueInto("file", &path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var file *os.File
|
||||||
|
if path == "-" {
|
||||||
|
file = os.Stdin
|
||||||
|
} else {
|
||||||
|
path = ExpandPath(path)
|
||||||
|
file, err = os.Open(path) // nolint: gosec
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target.Set(reflect.ValueOf(file))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func existingFileMapper(r *Registry) MapperFunc {
|
func existingFileMapper(r *Registry) MapperFunc {
|
||||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||||
if target.Kind() == reflect.Slice {
|
if target.Kind() == reflect.Slice {
|
||||||
|
|||||||
@@ -345,3 +345,21 @@ func TestNumbers(t *testing.T) {
|
|||||||
}, cli)
|
}, cli)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFileMapper(t *testing.T) {
|
||||||
|
type CLI struct {
|
||||||
|
File *os.File `arg:""`
|
||||||
|
}
|
||||||
|
var cli CLI
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
_, err := p.Parse([]string{"testdata/file.txt"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, cli.File)
|
||||||
|
_ = cli.File.Close()
|
||||||
|
_, err = p.Parse([]string{"testdata/missing.txt"})
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), "missing.txt: no such file or directory")
|
||||||
|
_, err = p.Parse([]string{"-"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, os.Stdin, cli.File)
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
Hello world.
|
||||||
Reference in New Issue
Block a user