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 }