Skip to content

Commit 73c9745

Browse files
author
Stefan Tudose
committed
add next and scan reading mode
1 parent 15fd4c8 commit 73c9745

File tree

6 files changed

+286
-69
lines changed

6 files changed

+286
-69
lines changed

convert.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package csvdecoder
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
"strconv"
7+
)
8+
9+
// convertAssignValues copies to dest the value in src, converting it if possible.
10+
// An error is returned if the conversion is not possible.
11+
// dest is expected to be a non-nil pointer type.
12+
func convertAssignValue(dest interface{}, src string) error {
13+
dpv := reflect.ValueOf(dest)
14+
if dpv.Kind() != reflect.Ptr {
15+
return errNotPtr
16+
}
17+
if dpv.IsNil() {
18+
return errNilPtr
19+
}
20+
21+
if src == "" {
22+
// zero values should leave the dest untouched
23+
return nil
24+
}
25+
26+
// check if the destination implements the Decoder interface
27+
if decoder, ok := dest.(Decoder); ok {
28+
return decoder.DecodeRecord(src)
29+
}
30+
31+
var sv reflect.Value
32+
33+
// simple cases without reflect
34+
switch d := dest.(type) {
35+
case *string:
36+
*d = src
37+
return nil
38+
case *[]byte:
39+
*d = []byte(src)
40+
return nil
41+
case *bool:
42+
bv, err := strconv.ParseBool(src)
43+
if err == nil {
44+
*d = bv
45+
}
46+
return err
47+
case *interface{}:
48+
*d = sv.Interface()
49+
return nil
50+
}
51+
52+
// cases with reflect
53+
sv = reflect.ValueOf(src)
54+
dv := reflect.Indirect(dpv)
55+
56+
if sv.IsValid() && sv.Type().AssignableTo(dv.Type()) {
57+
dv.Set(sv)
58+
return nil
59+
}
60+
61+
if dv.Kind() == sv.Kind() && sv.Type().ConvertibleTo(dv.Type()) {
62+
dv.Set(sv.Convert(dv.Type()))
63+
return nil
64+
}
65+
66+
switch dv.Kind() {
67+
case reflect.Ptr:
68+
dv.Set(reflect.New(dv.Type().Elem()))
69+
return convertAssignValue(dv.Interface(), src)
70+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
71+
i64, err := strconv.ParseInt(src, 10, dv.Type().Bits())
72+
if err != nil {
73+
return err
74+
}
75+
dv.SetInt(i64)
76+
return nil
77+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
78+
u64, err := strconv.ParseUint(src, 10, dv.Type().Bits())
79+
if err != nil {
80+
return err
81+
}
82+
dv.SetUint(u64)
83+
return nil
84+
case reflect.Float32, reflect.Float64:
85+
f64, err := strconv.ParseFloat(src, dv.Type().Bits())
86+
if err != nil {
87+
return err
88+
}
89+
dv.SetFloat(f64)
90+
return nil
91+
}
92+
93+
return fmt.Errorf("unsupported Scan, storing type %T into type %T", src, dest)
94+
}

errors.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package csvdecoder
2+
3+
import (
4+
"errors"
5+
)
6+
7+
var (
8+
// ErrEOF is thrown if the EOF is reached by the Next method.
9+
ErrEOF = errors.New("end of file reached")
10+
11+
errNilPtr = errors.New("destination is a nil pointer")
12+
errNotPtr = errors.New("destination not a pointer")
13+
)

main/main.go

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,9 @@ type ExampleStringArrayRow struct {
2121
}
2222

2323
type ExampleSimpleRow struct {
24-
Name string
25-
Age int
26-
Gender string
27-
Real bool
24+
Name string
25+
Age int
26+
Real bool
2827
}
2928

3029
type ExampleIntArrayRow struct {
@@ -67,26 +66,25 @@ type TestExampleDeserializerRow struct {
6766

6867
func main() {
6968
fmt.Println("running")
70-
file, err := os.Open("/Users/stefan.tudose/private/gitrepos/csvdecoder/example_simple.csvdecoder")
69+
file, err := os.Open("/Users/stefan.tudose/private/gitrepos/csvdecoder/test/example_simple.csv")
7170
if err != nil {
7271
panic(err)
7372
}
7473
defer file.Close()
7574

76-
parser, err := csvdecoder.NewParser(file)
75+
parser, err := csvdecoder.NewParser(file, &csvdecoder.ParserConfig{IgnoreHeaders: true})
7776
if err != nil {
7877
panic(err)
7978
}
8079

81-
for {
80+
for parser.Nexty() {
8281
data := ExampleSimpleRow{}
83-
eof, err := parser.Next(&data)
84-
if eof {
85-
return
86-
}
87-
if err != nil {
82+
if err := parser.Scan(&data.Name, &data.Age, &data.Real); err != nil {
8883
panic(err)
8984
}
9085
fmt.Println(data)
9186
}
87+
if err = parser.Err(); err != nil {
88+
panic(err)
89+
}
9290
}

parser.go

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ import (
1212
)
1313

1414
type Parser struct {
15-
Reader *csv.Reader
16-
config *ParserConfig
15+
Reader *csv.Reader
16+
config *ParserConfig
17+
currentRowValues []string
18+
lastErr error
1719
}
1820

1921
// ParserConfig has information for parser
@@ -168,3 +170,61 @@ func (p *Parser) Next(data interface{}) (eof bool, err error) {
168170

169171
return false, nil
170172
}
173+
174+
// Scan copies the values in the current row into the values pointed
175+
// at by dest. The number of values in dest can be different than the
176+
// number of values. In this case Scan will only fill the available
177+
// values.
178+
//
179+
// Scan converts columns read from the database into the following
180+
// common types:
181+
//
182+
// *string
183+
// *[]byte
184+
// *int, *int8, *int16, *int32, *int64
185+
// *uint, *uint8, *uint16, *uint32, *uint64
186+
// *bool
187+
// *float32, *float64
188+
// *interface{}
189+
// any type implementing Decoder
190+
//
191+
func (p *Parser) Scan(dest ...interface{}) error {
192+
if p.currentRowValues == nil {
193+
return errors.New("Scan called without calling Next")
194+
}
195+
for i, val := range p.currentRowValues {
196+
err := convertAssignValue(dest[i], val)
197+
if err != nil {
198+
return fmt.Errorf(`Scan error on value index %d: %v`, i, err)
199+
}
200+
}
201+
return nil
202+
}
203+
204+
// Next prepares the next result row for reading with the Scan method. It
205+
// returns nil on success, or false if there is no next result row or an error
206+
// happened while preparing it. Err should be consulted to distinguish between
207+
// the two cases.
208+
//
209+
// Every call to Scan, even the first one, must be preceded by a call to Next.
210+
func (p *Parser) Nexty() bool {
211+
var err error
212+
p.currentRowValues, err = p.Reader.Read()
213+
if err != nil {
214+
if err.Error() == "EOF" {
215+
p.lastErr = ErrEOF
216+
return false
217+
}
218+
p.lastErr = fmt.Errorf("error while reading: %w", err)
219+
return false
220+
}
221+
return true
222+
}
223+
224+
// Err returns the error, if any, that was encountered during iteration.
225+
func (p *Parser) Err() error {
226+
if p.lastErr != nil && p.lastErr != ErrEOF {
227+
return p.lastErr
228+
}
229+
return nil
230+
}

0 commit comments

Comments
 (0)