forked from rovaughn/fastcsv
-
Notifications
You must be signed in to change notification settings - Fork 0
/
fastcsv.go
125 lines (103 loc) · 2.21 KB
/
fastcsv.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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package fastcsv
import (
"bytes"
"fmt"
"os"
"reflect"
"syscall"
)
type FileReader struct {
file *os.File
data []byte
current []byte
separator byte
dest []reflect.Value
}
func NewFileReader(filename string, separator byte, dest interface{}) (*FileReader, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
fi, err := f.Stat()
if err != nil {
return nil, err
}
data, err := syscall.Mmap(int(f.Fd()), 0, int(fi.Size()), syscall.PROT_READ, syscall.MAP_SHARED)
if err != nil {
return nil, err
}
r := &FileReader{
file: f,
data: data,
current: data,
separator: separator,
}
unsetFields := make(map[string]bool)
destFields := make(map[string]reflect.Value)
destValue := reflect.Indirect(reflect.ValueOf(dest))
for i := 0; i < destValue.NumField(); i++ {
field := destValue.Type().Field(i)
if name, ok := field.Tag.Lookup("csv"); ok {
destFields[name] = destValue.Field(i)
} else {
destFields[field.Name] = destValue.Field(i)
unsetFields[field.Name] = true
}
}
header := r.byteRecord()
r.dest = make([]reflect.Value, len(header))
for index, name := range header {
field, ok := destFields[string(name)]
if !ok {
continue
}
r.dest[index] = field
delete(unsetFields, string(name))
}
if len(unsetFields) > 0 {
return nil, fmt.Errorf("Not all fields present in csv")
}
return r, nil
}
func (r *FileReader) byteRecord() [][]byte {
eol := bytes.IndexByte(r.current, '\n')
if eol == -1 {
return nil
} else {
row := r.current[:eol]
r.current = r.current[eol+1:]
return bytes.Split(row, []byte{r.separator})
}
}
func (r *FileReader) Scan() bool {
if len(r.current) == 0 {
return false
}
nfield := 0
start := 0
i := 0
for {
if i >= len(r.current) || r.current[i] == '\n' || r.current[i] == r.separator {
if r.dest[nfield].CanSet() {
r.dest[nfield].SetBytes(r.current[start:i])
}
start = i + 1
nfield++
}
if i >= len(r.current) {
r.current = nil
break
} else if r.current[i] == '\n' {
r.current = r.current[i+1:]
break
}
i++
}
return nfield >= len(r.dest)
}
func (r *FileReader) Close() error {
var err error
err = syscall.Munmap(r.data)
err = r.file.Close()
return err
}