Skip to content

Commit 88ecc9c

Browse files
committedSep 8, 2020
Fix enum's from envars not validating (fixes alecthomas#107).
Also added a mapper for `*os.File`.
1 parent cbae65d commit 88ecc9c

File tree

7 files changed

+64
-7
lines changed

7 files changed

+64
-7
lines changed
 

‎README.md

+11-1
Original file line numberDiff line numberDiff line change
@@ -382,11 +382,21 @@ Slices and maps treat type tags specially. For slices, the `type:""` tag
382382
specifies the element type. For maps, the tag has the format
383383
`tag:"[<key>]:[<value>]"` where either may be omitted.
384384

385+
## Supported field types
386+
385387

386388
## Custom decoders (mappers)
387389

390+
388391
Any field implementing `encoding.TextUnmarshaler` or `json.Unmarshaler` will use those interfaces
389-
for decoding values.
392+
for decoding values. Kong also includes builtin support for many common Go types:
393+
394+
| Type | Description
395+
|---------------------|--------------------------------------------
396+
| `time.Duration` | Populated using `time.ParseDuration()`.
397+
| `time.Time` | Populated using `time.Parse()`. Format defaults to RFC3339 but can be overridden with the `format:"X"` tag.
398+
| `*os.File` | Path to a file that will be opened, or `-` for `os.Stdin`. File must be closed by the user.
399+
| `*url.URL` | Populated with `url.Parse()`.
390400

391401
For more fine-grained control, if a field implements the
392402
[MapperValue](https://godoc.org/github.com/alecthomas/kong#MapperValue)

‎context.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package kong
22

33
import (
44
"fmt"
5+
"os"
56
"reflect"
67
"sort"
78
"strconv"
@@ -136,7 +137,8 @@ func (c *Context) Empty() bool {
136137
func (c *Context) Validate() error { // nolint: gocyclo
137138
err := Visit(c.Model, func(node Visitable, next Next) error {
138139
if value, ok := node.(*Value); ok {
139-
if value.Enum != "" && (!value.Required || value.Default != "") {
140+
_, ok := os.LookupEnv(value.Tag.Env)
141+
if value.Enum != "" && (!value.Required || value.Default != "" || (value.Tag.Env != "" && ok)) {
140142
if err := checkEnum(value, value.Target); err != nil {
141143
return err
142144
}

‎hooks.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package kong
22

3-
// BeforeResolve is a documentation-only interface describing hooks that run before values are set.
3+
// BeforeResolve is a documentation-only interface describing hooks that run before resolvers are applied.
44
type BeforeResolve interface {
55
// This is not the correct signature - see README for details.
66
BeforeResolve(args ...interface{}) error

‎kong_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,7 @@ func TestEnvarEnumValidated(t *testing.T) {
743743
})
744744
defer restore()
745745
var cli struct {
746-
Flag string `env:"FLAG" enum:"valid" default:"valid"`
746+
Flag string `env:"FLAG" required:"" enum:"valid"`
747747
}
748748
p := mustNew(t, &cli)
749749
_, err := p.Parse(nil)
@@ -798,7 +798,7 @@ func TestIssue40EnumAcrossCommands(t *testing.T) {
798798
OneArg string `arg:"" required:""`
799799
} `cmd:""`
800800
Two struct {
801-
TwoArg string `arg:"" enum:"a,b,c" required:""`
801+
TwoArg string `arg:"" enum:"a,b,c" required:"" env:"FOO"`
802802
} `cmd:""`
803803
Three struct {
804804
ThreeArg string `arg:"" optional:"" default:"a" enum:"a,b,c"`

‎mapper.go

+28-2
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,8 @@ func (r *Registry) RegisterDefaults() *Registry {
231231
RegisterKind(reflect.Int16, intDecoder(16)).
232232
RegisterKind(reflect.Int32, intDecoder(32)).
233233
RegisterKind(reflect.Int64, intDecoder(64)).
234-
RegisterKind(reflect.Uint, uintDecoder(64)).
235-
RegisterKind(reflect.Uint8, uintDecoder(bits.UintSize)).
234+
RegisterKind(reflect.Uint, uintDecoder(bits.UintSize)).
235+
RegisterKind(reflect.Uint8, uintDecoder(8)).
236236
RegisterKind(reflect.Uint16, uintDecoder(16)).
237237
RegisterKind(reflect.Uint32, uintDecoder(32)).
238238
RegisterKind(reflect.Uint64, uintDecoder(64)).
@@ -248,6 +248,7 @@ func (r *Registry) RegisterDefaults() *Registry {
248248
RegisterType(reflect.TypeOf(time.Time{}), timeDecoder()).
249249
RegisterType(reflect.TypeOf(time.Duration(0)), durationDecoder()).
250250
RegisterType(reflect.TypeOf(&url.URL{}), urlMapper()).
251+
RegisterType(reflect.TypeOf(&os.File{}), fileMapper(r)).
251252
RegisterName("path", pathMapper(r)).
252253
RegisterName("existingfile", existingFileMapper(r)).
253254
RegisterName("existingdir", existingDirMapper(r)).
@@ -541,6 +542,31 @@ func pathMapper(r *Registry) MapperFunc {
541542
}
542543
}
543544

545+
func fileMapper(r *Registry) MapperFunc {
546+
return func(ctx *DecodeContext, target reflect.Value) error {
547+
if target.Kind() == reflect.Slice {
548+
return sliceDecoder(r)(ctx, target)
549+
}
550+
var path string
551+
err := ctx.Scan.PopValueInto("file", &path)
552+
if err != nil {
553+
return err
554+
}
555+
var file *os.File
556+
if path == "-" {
557+
file = os.Stdin
558+
} else {
559+
path = ExpandPath(path)
560+
file, err = os.Open(path) // nolint: gosec
561+
if err != nil {
562+
return err
563+
}
564+
}
565+
target.Set(reflect.ValueOf(file))
566+
return nil
567+
}
568+
}
569+
544570
func existingFileMapper(r *Registry) MapperFunc {
545571
return func(ctx *DecodeContext, target reflect.Value) error {
546572
if target.Kind() == reflect.Slice {

‎mapper_test.go

+18
Original file line numberDiff line numberDiff line change
@@ -345,3 +345,21 @@ func TestNumbers(t *testing.T) {
345345
}, cli)
346346
})
347347
}
348+
349+
func TestFileMapper(t *testing.T) {
350+
type CLI struct {
351+
File *os.File `arg:""`
352+
}
353+
var cli CLI
354+
p := mustNew(t, &cli)
355+
_, err := p.Parse([]string{"testdata/file.txt"})
356+
require.NoError(t, err)
357+
require.NotNil(t, cli.File)
358+
_ = cli.File.Close()
359+
_, err = p.Parse([]string{"testdata/missing.txt"})
360+
require.Error(t, err)
361+
require.Contains(t, err.Error(), "missing.txt: no such file or directory")
362+
_, err = p.Parse([]string{"-"})
363+
require.NoError(t, err)
364+
require.Equal(t, os.Stdin, cli.File)
365+
}

‎testdata/file.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello world.

0 commit comments

Comments
 (0)
Please sign in to comment.