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.