diff --git a/mapper.go b/mapper.go index 3f980c2..15ab88d 100644 --- a/mapper.go +++ b/mapper.go @@ -2,6 +2,7 @@ package kong import ( "fmt" + "io/ioutil" "math/bits" "net/url" "os" @@ -58,7 +59,7 @@ func (m *mapperValueAdapter) IsBool() bool { // // Mappers can be associated with concrete fields via pointer, reflect.Type, reflect.Kind, or via a "type" tag. // -// Additionally, if a type implements this interface, it will be used. +// Additionally, if a type implements the MappverValue interface, it will be used. type Mapper interface { // Decode ctx.Value with ctx.Scanner into target. Decode(ctx *DecodeContext, target reflect.Value) error @@ -462,3 +463,16 @@ func JoinEscaped(s []string, sep rune) string { } return strings.Join(escaped, string(sep)) } + +// FileContentFlag is a flag value that loads a file's contents into its value. +type FileContentFlag []byte + +func (f *FileContentFlag) Decode(ctx *DecodeContext) error { // nolint: golint + filename := ctx.Scan.PopValue("filename") + data, err := ioutil.ReadFile(filename) // nolint: gosec + if err != nil { + return fmt.Errorf("failed to open %q: %s", filename, err) + } + *f = FileContentFlag(data) + return nil +} diff --git a/mapper_test.go b/mapper_test.go index 86e3a41..6d393ad 100644 --- a/mapper_test.go +++ b/mapper_test.go @@ -1,7 +1,10 @@ package kong_test import ( + "fmt" + "io/ioutil" "net/url" + "os" "reflect" "testing" "time" @@ -149,3 +152,17 @@ func TestMapperValue(t *testing.T) { require.NoError(t, err) require.Equal(t, "foo", cli.Value.decoded) } + +func TestFileContentFlag(t *testing.T) { + var cli struct { + File kong.FileContentFlag + } + f, err := ioutil.TempFile("", "") + require.NoError(t, err) + defer os.Remove(f.Name()) + fmt.Fprint(f, "hello world") + f.Close() + _, err = mustNew(t, &cli).Parse([]string{"--file", f.Name()}) + require.NoError(t, err) + require.Equal(t, []byte("hello world"), []byte(cli.File)) +}