Skip to content

Commit

Permalink
Merge pull request #439 from hairyhenderson/suppress-empty-output-files
Browse files Browse the repository at this point in the history
Adding option to suppress empty output files
  • Loading branch information
hairyhenderson authored Nov 28, 2018
2 parents 6484933 + 7a1263c commit 2f357bd
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 0 deletions.
11 changes: 11 additions & 0 deletions docs/content/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,14 @@ add the command to the command-line after a `--` argument:
$ gomplate -i 'hello world' -o out.txt -- cat out.txt
hello world
```

## Suppressing empty output

Sometimes it can be desirable to suppress empty output (i.e. output consisting of only whitespace). To do so, set `GOMPLATE_SUPPRESS_EMPTY=true` in your environment:

```console
$ export GOMPLATE_SUPPRESS_EMPTY=true
$ gomplate -i '{{ print " \n" }}' -o out
$ cat out
cat: out: No such file or directory
```
83 changes: 83 additions & 0 deletions template.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package gomplate

import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"text/template"

"github.com/hairyhenderson/gomplate/conv"
"github.com/hairyhenderson/gomplate/env"
"github.com/pkg/errors"

"github.com/spf13/afero"
)

Expand Down Expand Up @@ -219,9 +224,23 @@ func inList(list []string, entry string) bool {
}

func openOutFile(filename string, mode os.FileMode, modeOverride bool) (out io.WriteCloser, err error) {
if conv.ToBool(env.Getenv("GOMPLATE_SUPPRESS_EMPTY", "false")) {
out = newEmptySkipper(func() (io.WriteCloser, error) {
if filename == "-" {
return Stdout, nil
}
return createOutFile(filename, mode, modeOverride)
})
return out, nil
}

if filename == "-" {
return Stdout, nil
}
return createOutFile(filename, mode, modeOverride)
}

func createOutFile(filename string, mode os.FileMode, modeOverride bool) (out io.WriteCloser, err error) {
out, err = fs.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode.Perm())
if err != nil {
return out, err
Expand Down Expand Up @@ -268,3 +287,67 @@ func executeCombinedGlob(globArray []string) ([]string, error) {

return combinedExcludes, nil
}

// emptySkipper is a io.WriteCloser wrapper that will only start writing once a
// non-whitespace byte has been encountered. The writer must be provided by the
// `open` func
type emptySkipper struct {
open func() (io.WriteCloser, error)

// internal
w io.WriteCloser
buf *bytes.Buffer
nw bool
}

func newEmptySkipper(open func() (io.WriteCloser, error)) *emptySkipper {
return &emptySkipper{
w: nil,
buf: &bytes.Buffer{},
nw: false,
open: open,
}
}

func (f *emptySkipper) Write(p []byte) (n int, err error) {
if !f.nw {
if allWhitespace(p) {
// buffer the whitespace
return f.buf.Write(p)
}

// first time around, so open the writer
f.nw = true
f.w, err = f.open()
if err != nil {
return 0, err
}
if f.w == nil {
return 0, errors.New("nil writer returned by open")
}
// empty the buffer into the wrapped writer
_, err = f.buf.WriteTo(f.w)
if err != nil {
return 0, err
}
}

return f.w.Write(p)
}

func (f *emptySkipper) Close() error {
if f.w != nil {
return f.w.Close()
}
return nil
}

func allWhitespace(p []byte) bool {
for _, b := range p {
if b == ' ' || b == '\t' || b == '\n' || b == '\r' || b == '\v' {
continue
}
return false
}
return true
}
57 changes: 57 additions & 0 deletions template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,3 +295,60 @@ func TestProcessTemplates(t *testing.T) {
fs.Remove("out")
}
}

func TestAllWhitespace(t *testing.T) {
testdata := []struct {
in []byte
expected bool
}{
{[]byte(" "), true},
{[]byte("foo"), false},
{[]byte(" \t\n\n\v\r\n"), true},
{[]byte(" foo "), false},
}

for _, d := range testdata {
assert.Equal(t, d.expected, allWhitespace(d.in))
}
}

func TestEmptySkipper(t *testing.T) {
testdata := []struct {
in []byte
empty bool
}{
{[]byte(" "), true},
{[]byte("foo"), false},
{[]byte(" \t\n\n\v\r\n"), true},
{[]byte(" foo "), false},
}

for _, d := range testdata {
w := &bufferCloser{&bytes.Buffer{}}
opened := false
f := newEmptySkipper(func() (io.WriteCloser, error) {
t.Logf("I got called %#v", w)
opened = true
return w, nil
})
n, err := f.Write(d.in)
assert.NoError(t, err)
assert.Equal(t, len(d.in), n)
if d.empty {
assert.Nil(t, f.w)
assert.False(t, opened)
} else {
assert.NotNil(t, f.w)
assert.True(t, opened)
assert.EqualValues(t, d.in, w.Bytes())
}
}
}

type bufferCloser struct {
*bytes.Buffer
}

func (b *bufferCloser) Close() error {
return nil
}
16 changes: 16 additions & 0 deletions tests/integration/basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,19 @@ func (s *BasicSuite) TestExecCommand(c *C) {
Out: "hello world",
})
}

func (s *BasicSuite) TestEmptyOutputSuppression(c *C) {
out := s.tmpDir.Join("out")
result := icmd.RunCmd(icmd.Command(GomplateBin,
"-i",
`{{print "\t \n\n\r\n\t\t \v\n"}}`,
"-o", out),
func(cmd *icmd.Cmd) {
cmd.Env = []string{
"GOMPLATE_SUPPRESS_EMPTY=true",
}
})
result.Assert(c, icmd.Expected{ExitCode: 0})
_, err := os.Stat(out)
assert.Equal(c, true, os.IsNotExist(err))
}

0 comments on commit 2f357bd

Please sign in to comment.