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 }