diff --git a/README.md b/README.md index 40cdbb7..389f006 100644 --- a/README.md +++ b/README.md @@ -485,6 +485,7 @@ function `NamedMapper(name, mapper)`. | `existingfile` | An existing file. ~ expansion is applied. `-` is accepted for stdin, and will be passed unaltered. | | `existingdir` | An existing directory. ~ expansion is applied. | | `counter` | Increment a numeric field. Useful for `-vvv`. Can accept `-s`, `--long` or `--long=N`. | +| `filecontent` | Read the file at path into the field. ~ expansion is applied. `-` is accepted for stdin, and will be passed unaltered. | Slices and maps treat type tags specially. For slices, the `type:""` tag diff --git a/mapper.go b/mapper.go index 06cb71f..a510e74 100644 --- a/mapper.go +++ b/mapper.go @@ -283,6 +283,7 @@ func (r *Registry) RegisterDefaults() *Registry { RegisterName("existingfile", existingFileMapper(r)). RegisterName("existingdir", existingDirMapper(r)). RegisterName("counter", counterMapper()). + RegisterName("filecontent", fileContentMapper(r)). RegisterKind(reflect.Ptr, ptrMapper{r}) } @@ -684,6 +685,39 @@ func existingDirMapper(r *Registry) MapperFunc { } } +func fileContentMapper(r *Registry) MapperFunc { + return func(ctx *DecodeContext, target reflect.Value) error { + if target.Kind() != reflect.Slice && target.Elem().Kind() != reflect.Uint8 { + return fmt.Errorf("\"filecontent\" must be applied to []byte not %s", target.Type()) + } + var path string + err := ctx.Scan.PopValueInto("file", &path) + if err != nil { + return err + } + + if !ctx.Value.Active || ctx.Value.Set { + // early return to avoid checking extra dirs that may not exist; + // this hack only works because the value provided on the cli is + // checked before the default value is checked (if default is set). + return nil + } + + var data []byte + if path != "-" { + path = ExpandPath(path) + data, err = ioutil.ReadFile(path) //nolint:gosec + } else { + data, err = ioutil.ReadAll(os.Stdin) + } + if err != nil { + return err + } + target.SetBytes(data) + return nil + } +} + type ptrMapper struct { r *Registry } diff --git a/mapper_test.go b/mapper_test.go index e86fed7..81818fc 100644 --- a/mapper_test.go +++ b/mapper_test.go @@ -469,6 +469,24 @@ func TestFileMapper(t *testing.T) { assert.NoError(t, err) assert.Equal(t, os.Stdin, cli.File) } +func TestFileContentMapper(t *testing.T) { + type CLI struct { + File []byte `type:"filecontent"` + } + var cli CLI + p := mustNew(t, &cli) + _, err := p.Parse([]string{"--file", "testdata/file.txt"}) + assert.NoError(t, err) + assert.Equal(t, []byte(`Hello world.`), cli.File) + p = mustNew(t, &cli) + _, err = p.Parse([]string{"--file", "testdata/missing.txt"}) + assert.Error(t, err) + assert.Contains(t, err.Error(), "missing.txt: no such file or directory") + p = mustNew(t, &cli) + _, err = p.Parse([]string{"--file", "testdata/"}) + assert.Error(t, err) + assert.Contains(t, err.Error(), "is a directory") +} //nolint:dupl func TestExistingFileMapper(t *testing.T) {