github.com/btwiuse/jiri@v0.0.0-20191125065820-53353bcfef54/textutil/writer.go (about) 1 // Copyright 2015 The Vanadium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package textutil 6 7 import ( 8 "bytes" 9 "io" 10 "unicode/utf8" 11 ) 12 13 // WriteFlusher is the interface that groups the basic Write and Flush methods. 14 // 15 // Flush is typically provided when Write calls perform buffering; Flush 16 // immediately outputs the buffered data. Flush must be called after the last 17 // call to Write, and may be called an arbitrary number of times before the last 18 // Write. 19 type WriteFlusher interface { 20 io.Writer 21 Flush() error 22 } 23 24 // PrefixWriter returns an io.Writer that wraps w, where the prefix is written 25 // out immediately before the first non-empty Write call. 26 func PrefixWriter(w io.Writer, prefix string) io.Writer { 27 return &prefixWriter{w, []byte(prefix)} 28 } 29 30 type prefixWriter struct { 31 w io.Writer 32 prefix []byte 33 } 34 35 func (w *prefixWriter) Write(data []byte) (int, error) { 36 if w.prefix != nil && len(data) > 0 { 37 w.w.Write(w.prefix) 38 w.prefix = nil 39 } 40 return w.w.Write(data) 41 } 42 43 // PrefixLineWriter returns a WriteFlusher that wraps w. Each occurrence of EOL 44 // (\f, \n, \r, \v, LineSeparator or ParagraphSeparator) causes the preceeding 45 // line to be written to w, with the given prefix, in a single Write call. Data 46 // without EOL is buffered until the next EOL or Flush call. Flush appends \n to 47 // buffered data that doesn't end in EOL. 48 // 49 // A single Write call on the returned WriteFlusher may result in zero or more 50 // Write calls on the underlying w. 51 // 52 // If w implements WriteFlusher, each Flush call on the returned WriteFlusher 53 // results in exactly one Flush call on the underlying w. 54 func PrefixLineWriter(w io.Writer, prefix string) WriteFlusher { 55 return &prefixLineWriter{w, []byte(prefix), len(prefix)} 56 } 57 58 type prefixLineWriter struct { 59 w io.Writer 60 buf []byte 61 prefixLen int 62 } 63 64 const eolRunesAsString = "\f\n\r\v" + string(LineSeparator) + string(ParagraphSeparator) 65 66 func (w *prefixLineWriter) Write(data []byte) (int, error) { 67 // Write requires that the return arg is in the range [0, len(data)], and we 68 // must return len(data) on success. 69 totalLen := len(data) 70 for len(data) > 0 { 71 index := bytes.IndexAny(data, eolRunesAsString) 72 if index == -1 { 73 // No EOL: buffer remaining data. 74 // TODO(toddw): Flush at a max size, to avoid unbounded growth? 75 w.buf = append(w.buf, data...) 76 return totalLen, nil 77 } 78 // Saw EOL: single Write of buffer + data including EOL. 79 _, eolSize := utf8.DecodeRune(data[index:]) 80 dataEnd := index + eolSize 81 w.buf = append(w.buf, data[:dataEnd]...) 82 data = data[dataEnd:] 83 _, err := w.w.Write(w.buf) 84 w.buf = w.buf[:w.prefixLen] // reset buf to prepare for the next line. 85 if err != nil { 86 return totalLen - len(data), err 87 } 88 } 89 return totalLen, nil 90 } 91 92 func (w *prefixLineWriter) Flush() (e error) { 93 defer func() { 94 if f, ok := w.w.(WriteFlusher); ok { 95 if err := f.Flush(); err != nil && e == nil { 96 e = err 97 } 98 } 99 }() 100 if len(w.buf) > w.prefixLen { 101 w.buf = append(w.buf, '\n') // add EOL to unterminated line. 102 _, err := w.w.Write(w.buf) 103 w.buf = w.buf[:w.prefixLen] // reset buf to prepare for the next line. 104 if err != nil { 105 return err 106 } 107 } 108 return nil 109 } 110 111 // ByteReplaceWriter returns an io.Writer that wraps w, where all occurrences of 112 // the old byte are replaced with the new string on Write calls. 113 func ByteReplaceWriter(w io.Writer, old byte, new string) io.Writer { 114 return &byteReplaceWriter{w, []byte{old}, []byte(new)} 115 } 116 117 type byteReplaceWriter struct { 118 w io.Writer 119 old, new []byte 120 } 121 122 func (w *byteReplaceWriter) Write(data []byte) (int, error) { 123 replaced := bytes.Replace(data, w.old, w.new, -1) 124 if len(replaced) == 0 { 125 return len(data), nil 126 } 127 // Write the replaced data, and return the number of bytes in data that were 128 // written out, based on the proportion of replaced data written. The 129 // important boundary cases are: 130 // If all replaced data was written, we return n=len(data). 131 // If not all replaced data was written, we return n<len(data). 132 n, err := w.w.Write(replaced) 133 return n * len(data) / len(replaced), err 134 } 135 136 // TODO(toddw): Add ReplaceWriter, which performs arbitrary string replacements. 137 // This will need to buffer data and have an extra Flush() method, since the old 138 // string may match across successive Write calls.