github.com/hairyhenderson/gomplate/v3@v3.11.7/internal/iohelpers/writers.go (about)

     1  package iohelpers
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"sync"
    10  )
    11  
    12  type emptySkipper struct {
    13  	open func() (io.Writer, error)
    14  
    15  	// internal
    16  	w   io.Writer
    17  	buf *bytes.Buffer
    18  	nw  bool
    19  }
    20  
    21  // NewEmptySkipper creates an io.WriteCloser that will only start writing once a
    22  // non-whitespace byte has been encountered. The wrapped io.WriteCloser must be
    23  // provided by the `open` func.
    24  func NewEmptySkipper(open func() (io.Writer, error)) io.WriteCloser {
    25  	return &emptySkipper{
    26  		w:    nil,
    27  		buf:  &bytes.Buffer{},
    28  		nw:   false,
    29  		open: open,
    30  	}
    31  }
    32  
    33  func (f *emptySkipper) Write(p []byte) (n int, err error) {
    34  	if !f.nw {
    35  		if allWhitespace(p) {
    36  			// buffer the whitespace
    37  			return f.buf.Write(p)
    38  		}
    39  
    40  		// first time around, so open the writer
    41  		f.nw = true
    42  		f.w, err = f.open()
    43  		if err != nil {
    44  			return 0, err
    45  		}
    46  		if f.w == nil {
    47  			return 0, errors.New("nil writer returned by open")
    48  		}
    49  		// empty the buffer into the wrapped writer
    50  		_, err = f.buf.WriteTo(f.w)
    51  		if err != nil {
    52  			return 0, err
    53  		}
    54  	}
    55  
    56  	return f.w.Write(p)
    57  }
    58  
    59  // Close - implements io.Closer
    60  func (f *emptySkipper) Close() error {
    61  	if wc, ok := f.w.(io.WriteCloser); ok {
    62  		return wc.Close()
    63  	}
    64  	return nil
    65  }
    66  
    67  func allWhitespace(p []byte) bool {
    68  	for _, b := range p {
    69  		if b == ' ' || b == '\t' || b == '\n' || b == '\r' || b == '\v' {
    70  			continue
    71  		}
    72  		return false
    73  	}
    74  	return true
    75  }
    76  
    77  // NopCloser returns a WriteCloser with a no-op Close method wrapping
    78  // the provided io.Writer.
    79  type NopCloser struct {
    80  	io.Writer
    81  }
    82  
    83  // Close - implements io.Closer
    84  func (n *NopCloser) Close() error {
    85  	return nil
    86  }
    87  
    88  var (
    89  	_ io.WriteCloser = (*NopCloser)(nil)
    90  	_ io.WriteCloser = (*emptySkipper)(nil)
    91  	_ io.WriteCloser = (*sameSkipper)(nil)
    92  )
    93  
    94  type sameSkipper struct {
    95  	open func() (io.WriteCloser, error)
    96  
    97  	// internal
    98  	r    *bufio.Reader
    99  	w    io.WriteCloser
   100  	buf  *bytes.Buffer
   101  	diff bool
   102  }
   103  
   104  // SameSkipper creates an io.WriteCloser that will only start writing once a
   105  // difference with the current output has been encountered. The wrapped
   106  // io.WriteCloser must be provided by 'open'.
   107  func SameSkipper(r io.Reader, open func() (io.WriteCloser, error)) io.WriteCloser {
   108  	br := bufio.NewReader(r)
   109  	return &sameSkipper{
   110  		r:    br,
   111  		w:    nil,
   112  		buf:  &bytes.Buffer{},
   113  		diff: false,
   114  		open: open,
   115  	}
   116  }
   117  
   118  // Write - writes to the buffer, until a difference with the output is found,
   119  // then flushes and writes to the wrapped writer.
   120  func (f *sameSkipper) Write(p []byte) (n int, err error) {
   121  	if !f.diff {
   122  		in := make([]byte, len(p))
   123  		_, err := f.r.Read(in)
   124  		if err != nil && err != io.EOF {
   125  			return 0, fmt.Errorf("failed to read: %w", err)
   126  		}
   127  		if bytes.Equal(in, p) {
   128  			return f.buf.Write(p)
   129  		}
   130  
   131  		f.diff = true
   132  		err = f.flush()
   133  		if err != nil {
   134  			return 0, err
   135  		}
   136  	}
   137  	return f.w.Write(p)
   138  }
   139  
   140  func (f *sameSkipper) flush() (err error) {
   141  	if f.w == nil {
   142  		f.w, err = f.open()
   143  		if err != nil {
   144  			return err
   145  		}
   146  		if f.w == nil {
   147  			return fmt.Errorf("nil writer returned by open")
   148  		}
   149  	}
   150  	// empty the buffer into the wrapped writer
   151  	_, err = f.buf.WriteTo(f.w)
   152  	return err
   153  }
   154  
   155  // Close - implements io.Closer
   156  func (f *sameSkipper) Close() error {
   157  	// Check to see if we missed anything in the reader
   158  	if !f.diff {
   159  		n, err := f.r.Peek(1)
   160  		if len(n) > 0 || err != io.EOF {
   161  			err = f.flush()
   162  			if err != nil {
   163  				return fmt.Errorf("failed to flush on close: %w", err)
   164  			}
   165  		}
   166  	}
   167  
   168  	if f.w != nil {
   169  		return f.w.Close()
   170  	}
   171  	return nil
   172  }
   173  
   174  // LazyWriteCloser provides an interface to a WriteCloser that will open on the
   175  // first access. The wrapped io.WriteCloser must be provided by 'open'.
   176  func LazyWriteCloser(open func() (io.WriteCloser, error)) io.WriteCloser {
   177  	return &lazyWriteCloser{
   178  		opened: sync.Once{},
   179  		open:   open,
   180  	}
   181  }
   182  
   183  type lazyWriteCloser struct {
   184  	w io.WriteCloser
   185  	// caches the error that came from open(), if any
   186  	openErr error
   187  	open    func() (io.WriteCloser, error)
   188  	opened  sync.Once
   189  }
   190  
   191  var _ io.WriteCloser = (*lazyWriteCloser)(nil)
   192  
   193  func (l *lazyWriteCloser) openWriter() (r io.WriteCloser, err error) {
   194  	l.opened.Do(func() {
   195  		l.w, l.openErr = l.open()
   196  	})
   197  	return l.w, l.openErr
   198  }
   199  
   200  func (l *lazyWriteCloser) Close() error {
   201  	w, err := l.openWriter()
   202  	if err != nil {
   203  		return err
   204  	}
   205  	return w.Close()
   206  }
   207  
   208  func (l *lazyWriteCloser) Write(p []byte) (n int, err error) {
   209  	w, err := l.openWriter()
   210  	if err != nil {
   211  		return 0, err
   212  	}
   213  	return w.Write(p)
   214  }