/
tsv.go
82 lines (73 loc) · 1.85 KB
/
tsv.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package tsv
import (
"bufio"
"io"
"reflect"
"sync"
"strings"
"github.com/fatih/structs"
"github.com/miku/span/xio"
)
// A Decoder reads and decodes TSV rows from an input stream.
type Decoder struct {
Header []string // Column names.
Separator string // Field separator.
r *xio.SkipReader // The underlying reader.
once sync.Once
}
// NewDecoder returns a new decoder with tab as field separator.
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{r: xio.NewSkipReader(bufio.NewReader(r)), Separator: "\t"}
}
// NewDecoderSeparator creates a new decoder with a given separator.
func NewDecoderSeparator(r io.Reader, sep string) *Decoder {
return &Decoder{r: xio.NewSkipReader(bufio.NewReader(r)), Separator: sep}
}
// readHeader attempts to read the first row and store the column names. If the
// header has been already set manually, the values won't be overwritten.
func (dec *Decoder) readHeader() (err error) {
dec.once.Do(func() {
if len(dec.Header) > 0 {
return
}
var line string
if line, err = dec.r.ReadString('\n'); err != nil {
return
}
dec.Header = strings.Split(line, dec.Separator)
})
return
}
// Decode a single entry, reuse csv struct tags.
func (dec *Decoder) Decode(v interface{}) error {
if err := dec.readHeader(); err != nil {
return err
}
if reflect.TypeOf(v).Elem().Kind() != reflect.Struct {
return nil
}
line, err := dec.r.ReadString('\n')
if err == io.EOF {
return io.EOF
}
record := strings.Split(line, dec.Separator)
s := structs.New(v)
for _, f := range s.Fields() {
tag := f.Tag("csv")
if tag == "" || tag == "-" {
continue
}
for i, header := range dec.Header {
if tag != header {
continue
}
if i >= len(record) {
break // Record has too few columns.
}
if err := f.Set(record[i]); err != nil {
return err
}
}
}
return nil
}