diff --git a/mapper.go b/mapper.go index 875e438..01343b0 100644 --- a/mapper.go +++ b/mapper.go @@ -721,6 +721,33 @@ func JoinEscaped(s []string, sep rune) string { return strings.Join(escaped, string(sep)) } +// NamedFileContentFlag is a flag value that loads a file's contents and filename into its value. +type NamedFileContentFlag struct { + Filename string + Contents []byte +} + +func (f *NamedFileContentFlag) Decode(ctx *DecodeContext) error { // nolint: golint + var filename string + err := ctx.Scan.PopValueInto("filename", &filename) + if err != nil { + return err + } + // This allows unsetting of file content flags. + if filename == "" { + *f = NamedFileContentFlag{} + return nil + } + filename = ExpandPath(filename) + data, err := ioutil.ReadFile(filename) // nolint: gosec + if err != nil { + return errors.Errorf("failed to open %q: %s", filename, err) + } + f.Contents = data + f.Filename = filename + return nil +} + // FileContentFlag is a flag value that loads a file's contents into its value. type FileContentFlag []byte diff --git a/mapper_test.go b/mapper_test.go index ca4f42f..aecce76 100644 --- a/mapper_test.go +++ b/mapper_test.go @@ -240,6 +240,20 @@ func TestFileContentFlag(t *testing.T) { require.Equal(t, []byte("hello world"), []byte(cli.File)) } +func TestNamedFileContentFlag(t *testing.T) { + var cli struct { + File kong.NamedFileContentFlag + } + 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"), cli.File.Contents) +} + func TestNamedSliceTypesDontHaveEllipsis(t *testing.T) { var cli struct { File kong.FileContentFlag