summaryrefslogtreecommitdiff
path: root/vendor/github.com/gocarina/gocsv/decode.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/gocarina/gocsv/decode.go')
-rw-r--r--vendor/github.com/gocarina/gocsv/decode.go191
1 files changed, 160 insertions, 31 deletions
diff --git a/vendor/github.com/gocarina/gocsv/decode.go b/vendor/github.com/gocarina/gocsv/decode.go
index 980d7f7..a832e84 100644
--- a/vendor/github.com/gocarina/gocsv/decode.go
+++ b/vendor/github.com/gocarina/gocsv/decode.go
@@ -16,26 +16,7 @@ type Decoder interface {
// SimpleDecoder .
type SimpleDecoder interface {
getCSVRow() ([]string, error)
-}
-
-type decoder struct {
- in io.Reader
- csvDecoder *csvDecoder
-}
-
-func newDecoder(in io.Reader) *decoder {
- return &decoder{in: in}
-}
-
-func (decode *decoder) getCSVRows() ([][]string, error) {
- return getCSVReader(decode.in).ReadAll()
-}
-
-func (decode *decoder) getCSVRow() ([]string, error) {
- if decode.csvDecoder == nil {
- decode.csvDecoder = &csvDecoder{getCSVReader(decode.in)}
- }
- return decode.csvDecoder.Read()
+ getCSVRows() ([][]string, error)
}
type CSVReader interface {
@@ -47,6 +28,18 @@ type csvDecoder struct {
CSVReader
}
+func newSimpleDecoderFromReader(r io.Reader) SimpleDecoder {
+ return csvDecoder{getCSVReader(r)}
+}
+
+// NewSimpleDecoderFromCSVReader creates a SimpleDecoder, which may be passed
+// to the UnmarshalDecoder* family of functions, from a CSV reader. Note that
+// encoding/csv.Reader implements CSVReader, so you can pass one of those
+// directly here.
+func NewSimpleDecoderFromCSVReader(r CSVReader) SimpleDecoder {
+ return csvDecoder{r}
+}
+
func (c csvDecoder) getCSVRows() ([][]string, error) {
return c.ReadAll()
}
@@ -87,7 +80,7 @@ func mismatchHeaderFields(structInfo []fieldInfo, headers []string) []string {
return missing
}
- keyMap := make(map[string]struct{}, 0)
+ keyMap := make(map[string]struct{})
for _, info := range structInfo {
for _, key := range info.keys {
keyMap[key] = struct{}{}
@@ -115,14 +108,27 @@ func maybeDoubleHeaderNames(headers []string) error {
headerMap := make(map[string]bool, len(headers))
for _, v := range headers {
if _, ok := headerMap[v]; ok {
- return fmt.Errorf("Repeated header name: %v", v)
+ return fmt.Errorf("repeated header name: %v", v)
}
headerMap[v] = true
}
return nil
}
+// apply normalizer func to headers
+func normalizeHeaders(headers []string) []string {
+ out := make([]string, len(headers))
+ for i, h := range headers {
+ out[i] = normalizeName(h)
+ }
+ return out
+}
+
func readTo(decoder Decoder, out interface{}) error {
+ return readToWithErrorHandler(decoder, nil, out)
+}
+
+func readToWithErrorHandler(decoder Decoder, errHandler ErrorHandler, out interface{}) error {
outValue, outType := getConcreteReflectValueAndType(out) // Get the concrete type (not pointer) (Slice<?> or Array<?>)
if err := ensureOutType(outType); err != nil {
return err
@@ -146,7 +152,7 @@ func readTo(decoder Decoder, out interface{}) error {
return errors.New("no csv struct tags found")
}
- headers := csvRows[0]
+ headers := normalizeHeaders(csvRows[0])
body := csvRows[1:]
csvHeadersLabels := make(map[int]*fieldInfo, len(outInnerStructInfo.Fields)) // Used to store the correspondance header <-> position in CSV
@@ -174,34 +180,66 @@ func readTo(decoder Decoder, out interface{}) error {
}
}
+ var withFieldsOK bool
+ var fieldTypeUnmarshallerWithKeys TypeUnmarshalCSVWithFields
+
for i, csvRow := range body {
+ objectIface := reflect.New(outValue.Index(i).Type()).Interface()
outInner := createNewOutInner(outInnerWasPointer, outInnerType)
for j, csvColumnContent := range csvRow {
if fieldInfo, ok := csvHeadersLabels[j]; ok { // Position found accordingly to header name
+
+ if outInner.CanInterface() {
+ fieldTypeUnmarshallerWithKeys, withFieldsOK = objectIface.(TypeUnmarshalCSVWithFields)
+ if withFieldsOK {
+ if err := fieldTypeUnmarshallerWithKeys.UnmarshalCSVWithFields(fieldInfo.getFirstKey(), csvColumnContent); err != nil {
+ parseError := csv.ParseError{
+ Line: i + 2, //add 2 to account for the header & 0-indexing of arrays
+ Column: j + 1,
+ Err: err,
+ }
+ return &parseError
+ }
+ continue
+ }
+ }
+
if err := setInnerField(&outInner, outInnerWasPointer, fieldInfo.IndexChain, csvColumnContent, fieldInfo.omitEmpty); err != nil { // Set field of struct
- return &csv.ParseError{
+ parseError := csv.ParseError{
Line: i + 2, //add 2 to account for the header & 0-indexing of arrays
Column: j + 1,
Err: err,
}
+ if errHandler == nil || !errHandler(&parseError) {
+ return &parseError
+ }
}
}
}
+
+ if withFieldsOK {
+ reflectedObject := reflect.ValueOf(objectIface)
+ outInner = reflectedObject.Elem()
+ }
+
outValue.Index(i).Set(outInner)
}
return nil
}
func readEach(decoder SimpleDecoder, c interface{}) error {
+ outValue, outType := getConcreteReflectValueAndType(c) // Get the concrete type (not pointer)
+ if outType.Kind() != reflect.Chan {
+ return fmt.Errorf("cannot use %v with type %s, only channel supported", c, outType)
+ }
+ defer outValue.Close()
+
headers, err := decoder.getCSVRow()
if err != nil {
return err
}
- outValue, outType := getConcreteReflectValueAndType(c) // Get the concrete type (not pointer) (Slice<?> or Array<?>)
- if err := ensureOutType(outType); err != nil {
- return err
- }
- defer outValue.Close()
+ headers = normalizeHeaders(headers)
+
outInnerWasPointer, outInnerType := getConcreteContainerInnerType(outType) // Get the concrete inner type (not pointer) (Container<"?">)
if err := ensureOutInnerType(outInnerType); err != nil {
return err
@@ -258,6 +296,89 @@ func readEach(decoder SimpleDecoder, c interface{}) error {
return nil
}
+func readEachWithoutHeaders(decoder SimpleDecoder, c interface{}) error {
+ outValue, outType := getConcreteReflectValueAndType(c) // Get the concrete type (not pointer) (Slice<?> or Array<?>)
+ if err := ensureOutType(outType); err != nil {
+ return err
+ }
+ defer outValue.Close()
+
+ outInnerWasPointer, outInnerType := getConcreteContainerInnerType(outType) // Get the concrete inner type (not pointer) (Container<"?">)
+ if err := ensureOutInnerType(outInnerType); err != nil {
+ return err
+ }
+ outInnerStructInfo := getStructInfo(outInnerType) // Get the inner struct info to get CSV annotations
+ if len(outInnerStructInfo.Fields) == 0 {
+ return errors.New("no csv struct tags found")
+ }
+
+ i := 0
+ for {
+ line, err := decoder.getCSVRow()
+ if err == io.EOF {
+ break
+ } else if err != nil {
+ return err
+ }
+ outInner := createNewOutInner(outInnerWasPointer, outInnerType)
+ for j, csvColumnContent := range line {
+ fieldInfo := outInnerStructInfo.Fields[j]
+ if err := setInnerField(&outInner, outInnerWasPointer, fieldInfo.IndexChain, csvColumnContent, fieldInfo.omitEmpty); err != nil { // Set field of struct
+ return &csv.ParseError{
+ Line: i + 2, //add 2 to account for the header & 0-indexing of arrays
+ Column: j + 1,
+ Err: err,
+ }
+ }
+ }
+ outValue.Send(outInner)
+ i++
+ }
+ return nil
+}
+
+func readToWithoutHeaders(decoder Decoder, out interface{}) error {
+ outValue, outType := getConcreteReflectValueAndType(out) // Get the concrete type (not pointer) (Slice<?> or Array<?>)
+ if err := ensureOutType(outType); err != nil {
+ return err
+ }
+ outInnerWasPointer, outInnerType := getConcreteContainerInnerType(outType) // Get the concrete inner type (not pointer) (Container<"?">)
+ if err := ensureOutInnerType(outInnerType); err != nil {
+ return err
+ }
+ csvRows, err := decoder.getCSVRows() // Get the CSV csvRows
+ if err != nil {
+ return err
+ }
+ if len(csvRows) == 0 {
+ return errors.New("empty csv file given")
+ }
+ if err := ensureOutCapacity(&outValue, len(csvRows)+1); err != nil { // Ensure the container is big enough to hold the CSV content
+ return err
+ }
+ outInnerStructInfo := getStructInfo(outInnerType) // Get the inner struct info to get CSV annotations
+ if len(outInnerStructInfo.Fields) == 0 {
+ return errors.New("no csv struct tags found")
+ }
+
+ for i, csvRow := range csvRows {
+ outInner := createNewOutInner(outInnerWasPointer, outInnerType)
+ for j, csvColumnContent := range csvRow {
+ fieldInfo := outInnerStructInfo.Fields[j]
+ if err := setInnerField(&outInner, outInnerWasPointer, fieldInfo.IndexChain, csvColumnContent, fieldInfo.omitEmpty); err != nil { // Set field of struct
+ return &csv.ParseError{
+ Line: i + 1,
+ Column: j + 1,
+ Err: err,
+ }
+ }
+ }
+ outValue.Index(i).Set(outInner)
+ }
+
+ return nil
+}
+
// Check if the outType is an array or a slice
func ensureOutType(outType reflect.Type) error {
switch outType.Kind() {
@@ -302,9 +423,8 @@ func getCSVFieldPosition(key string, structInfo *structInfo, curHeaderCount int)
if field.matchesKey(key) {
if matchedFieldCount >= curHeaderCount {
return &field
- } else {
- matchedFieldCount++
}
+ matchedFieldCount++
}
}
return nil
@@ -320,7 +440,16 @@ func createNewOutInner(outInnerWasPointer bool, outInnerType reflect.Type) refle
func setInnerField(outInner *reflect.Value, outInnerWasPointer bool, index []int, value string, omitEmpty bool) error {
oi := *outInner
if outInnerWasPointer {
+ // initialize nil pointer
+ if oi.IsNil() {
+ setField(oi, "", omitEmpty)
+ }
oi = outInner.Elem()
}
+ // because pointers can be nil need to recurse one index at a time and perform nil check
+ if len(index) > 1 {
+ nextField := oi.Field(index[0])
+ return setInnerField(&nextField, nextField.Kind() == reflect.Ptr, index[1:], value, omitEmpty)
+ }
return setField(oi.FieldByIndex(index), value, omitEmpty)
}