github.com/hairyhenderson/gomplate/v4@v4.0.0-pre-2.0.20240520121557-362f058f0c93/internal/iohelpers/writers.go (about)

     1  package iohelpers
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"io/fs"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"sync"
    14  
    15  	"github.com/hack-pad/hackpadfs"
    16  )
    17  
    18  type emptySkipper struct {
    19  	open func() (io.Writer, error)
    20  
    21  	// internal
    22  	w   io.Writer
    23  	buf *bytes.Buffer
    24  	nw  bool
    25  }
    26  
    27  // NewEmptySkipper creates an io.WriteCloser that will only start writing once a
    28  // non-whitespace byte has been encountered. The wrapped io.WriteCloser must be
    29  // provided by the `open` func.
    30  func NewEmptySkipper(open func() (io.Writer, error)) io.WriteCloser {
    31  	return &emptySkipper{
    32  		w:    nil,
    33  		buf:  &bytes.Buffer{},
    34  		nw:   false,
    35  		open: open,
    36  	}
    37  }
    38  
    39  func (f *emptySkipper) Write(p []byte) (n int, err error) {
    40  	if !f.nw {
    41  		if allWhitespace(p) {
    42  			// buffer the whitespace
    43  			return f.buf.Write(p)
    44  		}
    45  
    46  		// first time around, so open the writer
    47  		f.nw = true
    48  		f.w, err = f.open()
    49  		if err != nil {
    50  			return 0, err
    51  		}
    52  		if f.w == nil {
    53  			return 0, errors.New("nil writer returned by open")
    54  		}
    55  		// empty the buffer into the wrapped writer
    56  		_, err = f.buf.WriteTo(f.w)
    57  		if err != nil {
    58  			return 0, err
    59  		}
    60  	}
    61  
    62  	return f.w.Write(p)
    63  }
    64  
    65  // Close - implements io.Closer
    66  func (f *emptySkipper) Close() error {
    67  	if wc, ok := f.w.(io.WriteCloser); ok {
    68  		return wc.Close()
    69  	}
    70  	return nil
    71  }
    72  
    73  func allWhitespace(p []byte) bool {
    74  	for _, b := range p {
    75  		if b == ' ' || b == '\t' || b == '\n' || b == '\r' || b == '\v' {
    76  			continue
    77  		}
    78  		return false
    79  	}
    80  	return true
    81  }
    82  
    83  // NopCloser returns a WriteCloser with a no-op Close method wrapping
    84  // the provided io.Writer.
    85  func NopCloser(w io.Writer) io.WriteCloser {
    86  	return &nopCloser{Writer: w}
    87  }
    88  
    89  type nopCloser struct {
    90  	io.Writer
    91  }
    92  
    93  // Close - implements io.Closer
    94  func (n *nopCloser) Close() error {
    95  	return nil
    96  }
    97  
    98  var (
    99  	_ io.WriteCloser = (*nopCloser)(nil)
   100  	_ io.WriteCloser = (*emptySkipper)(nil)
   101  	_ io.WriteCloser = (*sameSkipper)(nil)
   102  )
   103  
   104  type sameSkipper struct {
   105  	open func() (io.WriteCloser, error)
   106  
   107  	// internal
   108  	r    *bufio.Reader
   109  	w    io.WriteCloser
   110  	buf  *bytes.Buffer
   111  	diff bool
   112  }
   113  
   114  // SameSkipper creates an io.WriteCloser that will only start writing once a
   115  // difference with the current output has been encountered. The wrapped
   116  // io.WriteCloser must be provided by 'open'.
   117  func SameSkipper(r io.Reader, open func() (io.WriteCloser, error)) io.WriteCloser {
   118  	br := bufio.NewReader(r)
   119  	return &sameSkipper{
   120  		r:    br,
   121  		w:    nil,
   122  		buf:  &bytes.Buffer{},
   123  		diff: false,
   124  		open: open,
   125  	}
   126  }
   127  
   128  // Write - writes to the buffer, until a difference with the output is found,
   129  // then flushes and writes to the wrapped writer.
   130  func (f *sameSkipper) Write(p []byte) (n int, err error) {
   131  	if !f.diff {
   132  		in := make([]byte, len(p))
   133  		_, err := f.r.Read(in)
   134  		if err != nil && err != io.EOF {
   135  			return 0, fmt.Errorf("failed to read: %w", err)
   136  		}
   137  		if bytes.Equal(in, p) {
   138  			return f.buf.Write(p)
   139  		}
   140  
   141  		f.diff = true
   142  		err = f.flush()
   143  		if err != nil {
   144  			return 0, err
   145  		}
   146  	}
   147  	return f.w.Write(p)
   148  }
   149  
   150  func (f *sameSkipper) flush() (err error) {
   151  	if f.w == nil {
   152  		f.w, err = f.open()
   153  		if err != nil {
   154  			return err
   155  		}
   156  		if f.w == nil {
   157  			return fmt.Errorf("nil writer returned by open")
   158  		}
   159  	}
   160  	// empty the buffer into the wrapped writer
   161  	_, err = f.buf.WriteTo(f.w)
   162  	return err
   163  }
   164  
   165  // Close - implements io.Closer
   166  func (f *sameSkipper) Close() error {
   167  	// Check to see if we missed anything in the reader
   168  	if !f.diff {
   169  		n, err := f.r.Peek(1)
   170  		if len(n) > 0 || err != io.EOF {
   171  			err = f.flush()
   172  			if err != nil {
   173  				return fmt.Errorf("failed to flush on close: %w", err)
   174  			}
   175  		}
   176  	}
   177  
   178  	if f.w != nil {
   179  		return f.w.Close()
   180  	}
   181  	return nil
   182  }
   183  
   184  // LazyWriteCloser provides an interface to a WriteCloser that will open on the
   185  // first access. The wrapped io.WriteCloser must be provided by 'open'.
   186  func LazyWriteCloser(open func() (io.WriteCloser, error)) io.WriteCloser {
   187  	return &lazyWriteCloser{
   188  		opened: sync.Once{},
   189  		open:   open,
   190  	}
   191  }
   192  
   193  type lazyWriteCloser struct {
   194  	w io.WriteCloser
   195  	// caches the error that came from open(), if any
   196  	openErr error
   197  	open    func() (io.WriteCloser, error)
   198  	opened  sync.Once
   199  }
   200  
   201  var _ io.WriteCloser = (*lazyWriteCloser)(nil)
   202  
   203  func (l *lazyWriteCloser) openWriter() (r io.WriteCloser, err error) {
   204  	l.opened.Do(func() {
   205  		l.w, l.openErr = l.open()
   206  	})
   207  	return l.w, l.openErr
   208  }
   209  
   210  func (l *lazyWriteCloser) Close() error {
   211  	w, err := l.openWriter()
   212  	if err != nil {
   213  		return err
   214  	}
   215  	return w.Close()
   216  }
   217  
   218  func (l *lazyWriteCloser) Write(p []byte) (n int, err error) {
   219  	w, err := l.openWriter()
   220  	if err != nil {
   221  		return 0, err
   222  	}
   223  	return w.Write(p)
   224  }
   225  
   226  // WriteFile writes the given content to the file, truncating any existing file,
   227  // and creating the directory structure leading up to it if necessary.
   228  func WriteFile(fsys fs.FS, filename string, content []byte) error {
   229  	err := assertPathInWD(filename)
   230  	if err != nil {
   231  		return fmt.Errorf("failed to open %s: %w", filename, err)
   232  	}
   233  
   234  	fi, err := fs.Stat(fsys, filename)
   235  	if err != nil && !errors.Is(err, fs.ErrNotExist) {
   236  		return fmt.Errorf("failed to stat %s: %w", filename, err)
   237  	}
   238  	mode := NormalizeFileMode(0o644)
   239  	if fi != nil {
   240  		mode = fi.Mode()
   241  	}
   242  
   243  	err = hackpadfs.MkdirAll(fsys, filepath.Dir(filename), 0o755)
   244  	if err != nil {
   245  		return fmt.Errorf("failed to make dirs for %s: %w", filename, err)
   246  	}
   247  
   248  	err = hackpadfs.WriteFullFile(fsys, filename, content, mode)
   249  	if err != nil {
   250  		return fmt.Errorf("failed to write %s: %w", filename, err)
   251  	}
   252  
   253  	return nil
   254  }
   255  
   256  func assertPathInWD(filename string) error {
   257  	wd, err := os.Getwd()
   258  	if err != nil {
   259  		return err
   260  	}
   261  	f, err := filepath.Abs(filename)
   262  	if err != nil {
   263  		return err
   264  	}
   265  	r, err := filepath.Rel(wd, f)
   266  	if err != nil {
   267  		return err
   268  	}
   269  	if strings.HasPrefix(r, "..") {
   270  		return fmt.Errorf("path %s not contained by working directory %s (rel: %s)", filename, wd, r)
   271  	}
   272  	return nil
   273  }