github.com/grailbio/base@v0.0.11/tsv/writer.go (about)

     1  // Copyright 2018 GRAIL, Inc.  All rights reserved.
     2  // Use of this source code is governed by the Apache-2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  package tsv
     6  
     7  import (
     8  	"bufio"
     9  	"fmt"
    10  	"io"
    11  	"strconv"
    12  )
    13  
    14  // Writer provides an efficient and concise way to append a field at a time to
    15  // a TSV.  However, note that it does NOT have a Write() method; the interface
    16  // is deliberately restricted.
    17  //
    18  // We force this to fill at least one cacheline to prevent false sharing when
    19  // make([]Writer, parallelism) is used.
    20  type Writer struct {
    21  	w       *bufio.Writer
    22  	line    []byte
    23  	padding [32]byte // nolint: megacheck, structcheck, staticcheck
    24  }
    25  
    26  // NewWriter creates a new tsv.Writer from an io.Writer.
    27  func NewWriter(w io.Writer) (tw *Writer) {
    28  	return &Writer{
    29  		w:    bufio.NewWriter(w),
    30  		line: make([]byte, 0, 256),
    31  	}
    32  }
    33  
    34  // WriteString appends the given string and a tab to the current line.  (It is
    35  // safe to use this to write multiple fields at a time.)
    36  func (w *Writer) WriteString(s string) {
    37  	w.line = append(w.line, s...)
    38  	w.line = append(w.line, '\t')
    39  }
    40  
    41  // WriteBytes appends the given []byte and a tab to the current line.
    42  func (w *Writer) WriteBytes(s []byte) {
    43  	w.line = append(w.line, s...)
    44  	w.line = append(w.line, '\t')
    45  }
    46  
    47  // WriteUint32 converts the given uint32 to a string, and appends that and a
    48  // tab to the current line.
    49  func (w *Writer) WriteUint32(ui uint32) {
    50  	w.WriteUint64(uint64(ui))
    51  }
    52  
    53  // WriteInt64 converts the given int64 to a string, and appends that and a
    54  // tab to the current line.
    55  func (w *Writer) WriteInt64(i int64) {
    56  	w.line = strconv.AppendInt(w.line, i, 10)
    57  	w.line = append(w.line, '\t')
    58  }
    59  
    60  // WriteUint64 converts the given uint64 to a string, and appends that and a
    61  // tab to the current line.
    62  func (w *Writer) WriteUint64(ui uint64) {
    63  	w.line = strconv.AppendUint(w.line, ui, 10)
    64  	w.line = append(w.line, '\t')
    65  }
    66  
    67  // WriteFloat64 converts the given float64 to a string with the given
    68  // strconv.AppendFloat parameters, and appends that and a tab to the current
    69  // line.
    70  func (w *Writer) WriteFloat64(f float64, fmt byte, prec int) {
    71  	w.line = strconv.AppendFloat(w.line, f, fmt, prec, 64)
    72  	w.line = append(w.line, '\t')
    73  }
    74  
    75  // WriteByte appends the given literal byte (no number->string conversion) and
    76  // a tab to the current line.
    77  func (w *Writer) WriteByte(b byte) { // "go vet" complaint expected
    78  	w.line = append(w.line, b)
    79  	w.line = append(w.line, '\t')
    80  }
    81  
    82  // WritePartialString appends a string WITHOUT the usual subsequent tab.  It
    83  // must be followed by a non-Partial Write at some point to end the field;
    84  // otherwise EndLine will clobber the last character.
    85  func (w *Writer) WritePartialString(s string) {
    86  	w.line = append(w.line, s...)
    87  }
    88  
    89  // WritePartialBytes appends a []byte WITHOUT the usual subsequent tab.  It
    90  // must be followed by a non-Partial Write at some point to end the field;
    91  // otherwise EndLine will clobber the last character.
    92  func (w *Writer) WritePartialBytes(s []byte) {
    93  	w.line = append(w.line, s...)
    94  }
    95  
    96  // WritePartialUint32 converts the given uint32 to a string, and appends that
    97  // WITHOUT the usual subsequent tab.  It must be followed by a non-Partial
    98  // Write at some point to end the field; otherwise EndLine will clobber the
    99  // last character.
   100  func (w *Writer) WritePartialUint32(ui uint32) {
   101  	w.line = strconv.AppendUint(w.line, uint64(ui), 10)
   102  }
   103  
   104  // WritePartialByte appends the given literal byte (no number->string
   105  // conversion) WITHOUT the usual subsequent tab.  It must be followed by a
   106  // non-Partial Write at some point to end the field; otherwise EndLine will
   107  // clobber the last character.
   108  func (w *Writer) WritePartialByte(b byte) {
   109  	w.line = append(w.line, b)
   110  }
   111  
   112  // WriteCsvUint32 converts the given uint32 to a string, and appends that and a
   113  // comma to the current line.
   114  func (w *Writer) WriteCsvUint32(ui uint32) {
   115  	w.line = strconv.AppendUint(w.line, uint64(ui), 10)
   116  	w.line = append(w.line, ',')
   117  }
   118  
   119  // WriteCsvByte appends the given literal byte (no number->string conversion)
   120  // and a comma to the current line.
   121  func (w *Writer) WriteCsvByte(b byte) {
   122  	w.line = append(w.line, b)
   123  	w.line = append(w.line, ',')
   124  }
   125  
   126  // (Other Csv functions will be added as they're needed.)
   127  
   128  // EndCsv finishes the current comma-separated field, converting the last comma
   129  // to a tab.  It must be nonempty.
   130  func (w *Writer) EndCsv() {
   131  	w.line[len(w.line)-1] = '\t'
   132  }
   133  
   134  // EndLine finishes the current line.  It must be nonempty.
   135  func (w *Writer) EndLine() (err error) {
   136  	w.line[len(w.line)-1] = '\n'
   137  	// Tried making less frequent Write calls, doesn't seem to help.
   138  	_, err = w.w.Write(w.line)
   139  	w.line = w.line[:0]
   140  	return
   141  }
   142  
   143  // Flush flushes all finished lines.
   144  func (w *Writer) Flush() error {
   145  	return w.w.Flush()
   146  }
   147  
   148  // Copy appends the entire contents of the given io.Reader (assumed to be
   149  // another TSV file).
   150  func (w *Writer) Copy(r io.Reader) error {
   151  	if len(w.line) != 0 {
   152  		return fmt.Errorf("Writer.Copy: current line is nonempty")
   153  	}
   154  	_, err := io.Copy(w.w, r)
   155  	return err
   156  }