diff --git a/gomplate_test.go b/gomplate_test.go index e84b965b..5acf4279 100644 --- a/gomplate_test.go +++ b/gomplate_test.go @@ -16,6 +16,8 @@ import ( "github.com/hairyhenderson/gomplate/v3/conv" "github.com/hairyhenderson/gomplate/v3/data" "github.com/hairyhenderson/gomplate/v3/env" + "github.com/hairyhenderson/gomplate/v3/internal/writers" + "github.com/stretchr/testify/assert" ) @@ -156,7 +158,7 @@ func TestCustomDelim(t *testing.T) { func TestRunTemplates(t *testing.T) { defer func() { Stdout = os.Stdout }() buf := &bytes.Buffer{} - Stdout = &nopWCloser{buf} + Stdout = &writers.NopCloser{Writer: buf} config := &Config{Input: "foo", OutputFiles: []string{"-"}} err := RunTemplates(config) assert.NoError(t, err) diff --git a/internal/writers/writers.go b/internal/writers/writers.go new file mode 100644 index 00000000..70ab6220 --- /dev/null +++ b/internal/writers/writers.go @@ -0,0 +1,88 @@ +package writers + +import ( + "bytes" + "errors" + "io" +) + +type emptySkipper struct { + open func() (io.WriteCloser, error) + + // internal + w io.WriteCloser + buf *bytes.Buffer + nw bool +} + +// NewEmptySkipper creates an io.WriteCloser that will only start writing once a +// non-whitespace byte has been encountered. The wrapped io.WriteCloser must be +// provided by the `open` func. +func NewEmptySkipper(open func() (io.WriteCloser, error)) io.WriteCloser { + 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) +} + +// Close - implements io.Closer +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 +} + +// NopCloser returns a WriteCloser with a no-op Close method wrapping +// the provided io.Writer. +type NopCloser struct { + io.Writer +} + +// Close - implements io.Closer +func (n *NopCloser) Close() error { + return nil +} + +var ( + _ io.WriteCloser = (*NopCloser)(nil) + _ io.WriteCloser = (*emptySkipper)(nil) +) diff --git a/internal/writers/writers_test.go b/internal/writers/writers_test.go new file mode 100644 index 00000000..df9ccd8f --- /dev/null +++ b/internal/writers/writers_test.go @@ -0,0 +1,67 @@ +package writers + +import ( + "bytes" + "io" + "testing" + + "github.com/stretchr/testify/assert" +) + +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, ok := NewEmptySkipper(func() (io.WriteCloser, error) { + opened = true + return w, nil + }).(*emptySkipper) + + assert.True(t, ok) + 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 +} diff --git a/template.go b/template.go index 138625fd..37e0c721 100644 --- a/template.go +++ b/template.go @@ -1,7 +1,6 @@ package gomplate import ( - "bytes" "fmt" "io" "io/ioutil" @@ -10,10 +9,9 @@ import ( "text/template" "github.com/hairyhenderson/gomplate/v3/internal/config" + "github.com/hairyhenderson/gomplate/v3/internal/writers" "github.com/hairyhenderson/gomplate/v3/tmpl" - "github.com/pkg/errors" - "github.com/spf13/afero" "github.com/zealic/xignore" ) @@ -103,7 +101,7 @@ func gatherTemplates(cfg *config.Config, outFileNamer func(string) (string, erro // --exec-pipe redirects standard out to the out pipe if cfg.OutWriter != nil { - Stdout = &nopWCloser{cfg.OutWriter} + Stdout = &writers.NopCloser{Writer: cfg.OutWriter} } switch { @@ -229,7 +227,7 @@ func fileToTemplates(inFile, outFile string, mode os.FileMode, modeOverride bool func openOutFile(cfg *config.Config, filename string, mode os.FileMode, modeOverride bool) (out io.WriteCloser, err error) { if cfg.SuppressEmpty { - out = newEmptySkipper(func() (io.WriteCloser, error) { + out = writers.NewEmptySkipper(func() (io.WriteCloser, error) { if filename == "-" { return Stdout, nil } @@ -275,76 +273,3 @@ func readInput(filename string) (string, error) { } return string(bytes), 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 -} - -// like ioutil.NopCloser(), except for io.WriteClosers... -type nopWCloser struct { - io.Writer -} - -func (n *nopWCloser) Close() error { - return nil -} diff --git a/template_test.go b/template_test.go index 5f10530d..b0ba3698 100644 --- a/template_test.go +++ b/template_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/hairyhenderson/gomplate/v3/internal/config" + "github.com/hairyhenderson/gomplate/v3/internal/writers" "github.com/spf13/afero" "github.com/stretchr/testify/assert" @@ -53,7 +54,7 @@ func TestOpenOutFile(t *testing.T) { assert.Equal(t, os.FileMode(0644), i.Mode()) defer func() { Stdout = os.Stdout }() - Stdout = &nopWCloser{&bytes.Buffer{}} + Stdout = &writers.NopCloser{Writer: &bytes.Buffer{}} f, err := openOutFile(cfg, "-", 0644, false) assert.NoError(t, err) @@ -247,59 +248,3 @@ 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) { - 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 -}