From f96b7fc1e15ad88c80814e6b153c74453098f499 Mon Sep 17 00:00:00 2001 From: Victor Häggqvist Date: Mon, 5 Mar 2018 22:19:22 +0100 Subject: update vendor to use dep --- vendor/github.com/gocarina/gocsv/.gitignore | 1 + vendor/github.com/gocarina/gocsv/LICENSE | 21 ++ vendor/github.com/gocarina/gocsv/README.md | 170 +++++++++ vendor/github.com/gocarina/gocsv/csv.go | 319 ++++++++++++++++ vendor/github.com/gocarina/gocsv/decode.go | 326 ++++++++++++++++ vendor/github.com/gocarina/gocsv/encode.go | 139 +++++++ vendor/github.com/gocarina/gocsv/reflect.go | 107 ++++++ vendor/github.com/gocarina/gocsv/safe_csv.go | 32 ++ vendor/github.com/gocarina/gocsv/types.go | 456 +++++++++++++++++++++++ vendor/github.com/gocarina/gocsv/unmarshaller.go | 97 +++++ 10 files changed, 1668 insertions(+) create mode 100644 vendor/github.com/gocarina/gocsv/.gitignore create mode 100644 vendor/github.com/gocarina/gocsv/LICENSE create mode 100644 vendor/github.com/gocarina/gocsv/README.md create mode 100644 vendor/github.com/gocarina/gocsv/csv.go create mode 100644 vendor/github.com/gocarina/gocsv/decode.go create mode 100644 vendor/github.com/gocarina/gocsv/encode.go create mode 100644 vendor/github.com/gocarina/gocsv/reflect.go create mode 100644 vendor/github.com/gocarina/gocsv/safe_csv.go create mode 100644 vendor/github.com/gocarina/gocsv/types.go create mode 100644 vendor/github.com/gocarina/gocsv/unmarshaller.go (limited to 'vendor/github.com/gocarina/gocsv') diff --git a/vendor/github.com/gocarina/gocsv/.gitignore b/vendor/github.com/gocarina/gocsv/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/vendor/github.com/gocarina/gocsv/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/vendor/github.com/gocarina/gocsv/LICENSE b/vendor/github.com/gocarina/gocsv/LICENSE new file mode 100644 index 0000000..052a371 --- /dev/null +++ b/vendor/github.com/gocarina/gocsv/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Jonathan Picques + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/gocarina/gocsv/README.md b/vendor/github.com/gocarina/gocsv/README.md new file mode 100644 index 0000000..c3fae62 --- /dev/null +++ b/vendor/github.com/gocarina/gocsv/README.md @@ -0,0 +1,170 @@ +Go CSV +===== + +The GoCSV package aims to provide easy serialization and deserialization functions to use CSV in Golang + +API and techniques inspired from https://godoc.org/gopkg.in/mgo.v2 + +[![GoDoc](https://godoc.org/github.com/gocarina/gocsv?status.png)](https://godoc.org/github.com/gocarina/gocsv) +[![Build Status](https://travis-ci.org/gocarina/gocsv.svg?branch=master)](https://travis-ci.org/gocarina/gocsv) + +Installation +===== + +```go get -u github.com/gocarina/gocsv``` + +Full example +===== + +Consider the following CSV file + +```csv + +client_id,client_name,client_age +1,Jose,42 +2,Daniel,26 +3,Vincent,32 + +``` + +Easy binding in Go! +--- + +```go + +package main + +import ( + "fmt" + "gocsv" + "os" +) + +type Client struct { // Our example struct, you can use "-" to ignore a field + Id string `csv:"client_id"` + Name string `csv:"client_name"` + Age string `csv:"client_age"` + NotUsed string `csv:"-"` +} + +func main() { + clientsFile, err := os.OpenFile("clients.csv", os.O_RDWR|os.O_CREATE, os.ModePerm) + if err != nil { + panic(err) + } + defer clientsFile.Close() + + clients := []*Client{} + + if err := gocsv.UnmarshalFile(clientsFile, &clients); err != nil { // Load clients from file + panic(err) + } + for _, client := range clients { + fmt.Println("Hello", client.Name) + } + + if _, err := clientsFile.Seek(0, 0); err != nil { // Go to the start of the file + panic(err) + } + + clients = append(clients, &Client{Id: "12", Name: "John", Age: "21"}) // Add clients + clients = append(clients, &Client{Id: "13", Name: "Fred"}) + clients = append(clients, &Client{Id: "14", Name: "James", Age: "32"}) + clients = append(clients, &Client{Id: "15", Name: "Danny"}) + csvContent, err := gocsv.MarshalString(&clients) // Get all clients as CSV string + //err = gocsv.MarshalFile(&clients, clientsFile) // Use this to save the CSV back to the file + if err != nil { + panic(err) + } + fmt.Println(csvContent) // Display all clients as CSV string + +} + +``` + +Customizable Converters +--- + +```go + +type DateTime struct { + time.Time +} + +// Convert the internal date as CSV string +func (date *DateTime) MarshalCSV() (string, error) { + return date.Time.Format("20060201"), nil +} + +// You could also use the standard Stringer interface +func (date *DateTime) String() (string) { + return date.String() // Redundant, just for example +} + +// Convert the CSV string as internal date +func (date *DateTime) UnmarshalCSV(csv string) (err error) { + date.Time, err = time.Parse("20060201", csv) + if err != nil { + return err + } + return nil +} + +type Client struct { // Our example struct with a custom type (DateTime) + Id string `csv:"id"` + Name string `csv:"name"` + Employed DateTime `csv:"employed"` +} + +``` + +Customizable CSV Reader / Writer +--- + +```go + +func main() { + ... + + gocsv.SetCSVReader(func(in io.Reader) gocsv.CSVReader { + r := csv.NewReader(in) + r.Comma = '|' + return r // Allows use pipe as delimiter + }) + + ... + + gocsv.SetCSVReader(func(in io.Reader) gocsv.CSVReader { + r := csv.NewReader(in) + r.LazyQuotes = true + r.Comma = '.' + return r // Allows use dot as delimiter and use quotes in CSV + }) + + ... + + gocsv.SetCSVReader(func(in io.Reader) gocsv.CSVReader { + //return csv.NewReader(in) + return gocsv.LazyCSVReader(in) // Allows use of quotes in CSV + }) + + ... + + gocsv.UnmarshalFile(file, &clients) + + ... + + gocsv.SetCSVWriter(func(out io.Writer) *SafeCSVWriter { + writer := csv.NewWriter(out) + writer.Comma = '|' + return gocsv.NewSafeCSVWriter(writer) + }) + + ... + + gocsv.MarshalFile(&clients, file) + + ... +} + +``` diff --git a/vendor/github.com/gocarina/gocsv/csv.go b/vendor/github.com/gocarina/gocsv/csv.go new file mode 100644 index 0000000..2e336ea --- /dev/null +++ b/vendor/github.com/gocarina/gocsv/csv.go @@ -0,0 +1,319 @@ +// Copyright 2014 Jonathan Picques. All rights reserved. +// Use of this source code is governed by a MIT license +// The license can be found in the LICENSE file. + +// The GoCSV package aims to provide easy CSV serialization and deserialization to the golang programming language + +package gocsv + +import ( + "bytes" + "encoding/csv" + "fmt" + "io" + "os" + "reflect" + "strings" +) + +// FailIfUnmatchedStructTags indicates whether it is considered an error when there is an unmatched +// struct tag. +var FailIfUnmatchedStructTags = false + +// FailIfDoubleHeaderNames indicates whether it is considered an error when a header name is repeated +// in the csv header. +var FailIfDoubleHeaderNames = false + +// ShouldAlignDuplicateHeadersWithStructFieldOrder indicates whether we should align duplicate CSV +// headers per their alignment in the struct definition. +var ShouldAlignDuplicateHeadersWithStructFieldOrder = false + +// TagSeparator defines seperator string for multiple csv tags in struct fields +var TagSeparator = "," + +// -------------------------------------------------------------------------- +// CSVWriter used to format CSV + +var selfCSVWriter = DefaultCSVWriter + +// DefaultCSVWriter is the default SafeCSVWriter used to format CSV (cf. csv.NewWriter) +func DefaultCSVWriter(out io.Writer) *SafeCSVWriter { + writer := NewSafeCSVWriter(csv.NewWriter(out)) + + // As only one rune can be defined as a CSV separator, we are going to trim + // the custom tag separator and use the first rune. + if runes := []rune(strings.TrimSpace(TagSeparator)); len(runes) > 0 { + writer.Comma = runes[0] + } + + return writer +} + +// SetCSVWriter sets the SafeCSVWriter used to format CSV. +func SetCSVWriter(csvWriter func(io.Writer) *SafeCSVWriter) { + selfCSVWriter = csvWriter +} + +func getCSVWriter(out io.Writer) *SafeCSVWriter { + return selfCSVWriter(out) +} + +// -------------------------------------------------------------------------- +// CSVReader used to parse CSV + +var selfCSVReader = DefaultCSVReader + +// DefaultCSVReader is the default CSV reader used to parse CSV (cf. csv.NewReader) +func DefaultCSVReader(in io.Reader) CSVReader { + return csv.NewReader(in) +} + +// LazyCSVReader returns a lazy CSV reader, with LazyQuotes and TrimLeadingSpace. +func LazyCSVReader(in io.Reader) CSVReader { + csvReader := csv.NewReader(in) + csvReader.LazyQuotes = true + csvReader.TrimLeadingSpace = true + return csvReader +} + +// SetCSVReader sets the CSV reader used to parse CSV. +func SetCSVReader(csvReader func(io.Reader) CSVReader) { + selfCSVReader = csvReader +} + +func getCSVReader(in io.Reader) CSVReader { + return selfCSVReader(in) +} + +// -------------------------------------------------------------------------- +// Marshal functions + +// MarshalFile saves the interface as CSV in the file. +func MarshalFile(in interface{}, file *os.File) (err error) { + return Marshal(in, file) +} + +// MarshalString returns the CSV string from the interface. +func MarshalString(in interface{}) (out string, err error) { + bufferString := bytes.NewBufferString(out) + if err := Marshal(in, bufferString); err != nil { + return "", err + } + return bufferString.String(), nil +} + +// MarshalBytes returns the CSV bytes from the interface. +func MarshalBytes(in interface{}) (out []byte, err error) { + bufferString := bytes.NewBuffer(out) + if err := Marshal(in, bufferString); err != nil { + return nil, err + } + return bufferString.Bytes(), nil +} + +// Marshal returns the CSV in writer from the interface. +func Marshal(in interface{}, out io.Writer) (err error) { + writer := getCSVWriter(out) + return writeTo(writer, in, false) +} + +// Marshal returns the CSV in writer from the interface. +func MarshalWithoutHeaders(in interface{}, out io.Writer) (err error) { + writer := getCSVWriter(out) + return writeTo(writer, in, true) +} + +// MarshalChan returns the CSV read from the channel. +func MarshalChan(c <-chan interface{}, out *SafeCSVWriter) error { + return writeFromChan(out, c) +} + +// MarshalCSV returns the CSV in writer from the interface. +func MarshalCSV(in interface{}, out *SafeCSVWriter) (err error) { + return writeTo(out, in, false) +} + +// MarshalCSVWithoutHeaders returns the CSV in writer from the interface. +func MarshalCSVWithoutHeaders(in interface{}, out *SafeCSVWriter) (err error) { + return writeTo(out, in, true) +} + +// -------------------------------------------------------------------------- +// Unmarshal functions + +// UnmarshalFile parses the CSV from the file in the interface. +func UnmarshalFile(in *os.File, out interface{}) error { + return Unmarshal(in, out) +} + +// UnmarshalString parses the CSV from the string in the interface. +func UnmarshalString(in string, out interface{}) error { + return Unmarshal(strings.NewReader(in), out) +} + +// UnmarshalBytes parses the CSV from the bytes in the interface. +func UnmarshalBytes(in []byte, out interface{}) error { + return Unmarshal(bytes.NewReader(in), out) +} + +// Unmarshal parses the CSV from the reader in the interface. +func Unmarshal(in io.Reader, out interface{}) error { + return readTo(newDecoder(in), out) +} + +// UnmarshalDecoder parses the CSV from the decoder in the interface +func UnmarshalDecoder(in Decoder, out interface{}) error { + return readTo(in, out) +} + +// UnmarshalCSV parses the CSV from the reader in the interface. +func UnmarshalCSV(in CSVReader, out interface{}) error { + return readTo(csvDecoder{in}, out) +} + +// UnmarshalToChan parses the CSV from the reader and send each value in the chan c. +// The channel must have a concrete type. +func UnmarshalToChan(in io.Reader, c interface{}) error { + if c == nil { + return fmt.Errorf("goscv: channel is %v", c) + } + return readEach(newDecoder(in), c) +} + +// UnmarshalDecoderToChan parses the CSV from the decoder and send each value in the chan c. +// The channel must have a concrete type. +func UnmarshalDecoderToChan(in SimpleDecoder, c interface{}) error { + if c == nil { + return fmt.Errorf("goscv: channel is %v", c) + } + return readEach(in, c) +} + +// UnmarshalStringToChan parses the CSV from the string and send each value in the chan c. +// The channel must have a concrete type. +func UnmarshalStringToChan(in string, c interface{}) error { + return UnmarshalToChan(strings.NewReader(in), c) +} + +// UnmarshalBytesToChan parses the CSV from the bytes and send each value in the chan c. +// The channel must have a concrete type. +func UnmarshalBytesToChan(in []byte, c interface{}) error { + return UnmarshalToChan(bytes.NewReader(in), c) +} + +// UnmarshalToCallback parses the CSV from the reader and send each value to the given func f. +// The func must look like func(Struct). +func UnmarshalToCallback(in io.Reader, f interface{}) error { + valueFunc := reflect.ValueOf(f) + t := reflect.TypeOf(f) + if t.NumIn() != 1 { + return fmt.Errorf("the given function must have exactly one parameter") + } + cerr := make(chan error) + c := reflect.MakeChan(reflect.ChanOf(reflect.BothDir, t.In(0)), 0) + go func() { + cerr <- UnmarshalToChan(in, c.Interface()) + }() + for { + select { + case err := <-cerr: + return err + default: + } + v, notClosed := c.Recv() + if !notClosed || v.Interface() == nil { + break + } + valueFunc.Call([]reflect.Value{v}) + } + return nil +} + +// UnmarshalDecoderToCallback parses the CSV from the decoder and send each value to the given func f. +// The func must look like func(Struct). +func UnmarshalDecoderToCallback(in SimpleDecoder, f interface{}) error { + valueFunc := reflect.ValueOf(f) + t := reflect.TypeOf(f) + if t.NumIn() != 1 { + return fmt.Errorf("the given function must have exactly one parameter") + } + cerr := make(chan error) + c := reflect.MakeChan(reflect.ChanOf(reflect.BothDir, t.In(0)), 0) + go func() { + cerr <- UnmarshalDecoderToChan(in, c.Interface()) + }() + for { + select { + case err := <-cerr: + return err + default: + } + v, notClosed := c.Recv() + if !notClosed || v.Interface() == nil { + break + } + valueFunc.Call([]reflect.Value{v}) + } + return nil +} + +// UnmarshalBytesToCallback parses the CSV from the bytes and send each value to the given func f. +// The func must look like func(Struct). +func UnmarshalBytesToCallback(in []byte, f interface{}) error { + return UnmarshalToCallback(bytes.NewReader(in), f) +} + +// UnmarshalStringToCallback parses the CSV from the string and send each value to the given func f. +// The func must look like func(Struct). +func UnmarshalStringToCallback(in string, c interface{}) (err error) { + return UnmarshalToCallback(strings.NewReader(in), c) +} + +// CSVToMap creates a simple map from a CSV of 2 columns. +func CSVToMap(in io.Reader) (map[string]string, error) { + decoder := newDecoder(in) + header, err := decoder.getCSVRow() + if err != nil { + return nil, err + } + if len(header) != 2 { + return nil, fmt.Errorf("maps can only be created for csv of two columns") + } + m := make(map[string]string) + for { + line, err := decoder.getCSVRow() + if err == io.EOF { + break + } else if err != nil { + return nil, err + } + m[line[0]] = line[1] + } + return m, nil +} + +// CSVToMaps takes a reader and returns an array of dictionaries, using the header row as the keys +func CSVToMaps(reader io.Reader) ([]map[string]string, error) { + r := csv.NewReader(reader) + rows := []map[string]string{} + var header []string + for { + record, err := r.Read() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + if header == nil { + header = record + } else { + dict := map[string]string{} + for i := range header { + dict[header[i]] = record[i] + } + rows = append(rows, dict) + } + } + return rows, nil +} diff --git a/vendor/github.com/gocarina/gocsv/decode.go b/vendor/github.com/gocarina/gocsv/decode.go new file mode 100644 index 0000000..980d7f7 --- /dev/null +++ b/vendor/github.com/gocarina/gocsv/decode.go @@ -0,0 +1,326 @@ +package gocsv + +import ( + "encoding/csv" + "errors" + "fmt" + "io" + "reflect" +) + +// Decoder . +type Decoder interface { + getCSVRows() ([][]string, error) +} + +// 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() +} + +type CSVReader interface { + Read() ([]string, error) + ReadAll() ([][]string, error) +} + +type csvDecoder struct { + CSVReader +} + +func (c csvDecoder) getCSVRows() ([][]string, error) { + return c.ReadAll() +} + +func (c csvDecoder) getCSVRow() ([]string, error) { + return c.Read() +} + +func mismatchStructFields(structInfo []fieldInfo, headers []string) []string { + missing := make([]string, 0) + if len(structInfo) == 0 { + return missing + } + + headerMap := make(map[string]struct{}, len(headers)) + for idx := range headers { + headerMap[headers[idx]] = struct{}{} + } + + for _, info := range structInfo { + found := false + for _, key := range info.keys { + if _, ok := headerMap[key]; ok { + found = true + break + } + } + if !found { + missing = append(missing, info.keys...) + } + } + return missing +} + +func mismatchHeaderFields(structInfo []fieldInfo, headers []string) []string { + missing := make([]string, 0) + if len(headers) == 0 { + return missing + } + + keyMap := make(map[string]struct{}, 0) + for _, info := range structInfo { + for _, key := range info.keys { + keyMap[key] = struct{}{} + } + } + + for _, header := range headers { + if _, ok := keyMap[header]; !ok { + missing = append(missing, header) + } + } + return missing +} + +func maybeMissingStructFields(structInfo []fieldInfo, headers []string) error { + missing := mismatchStructFields(structInfo, headers) + if len(missing) != 0 { + return fmt.Errorf("found unmatched struct field with tags %v", missing) + } + return nil +} + +// Check that no header name is repeated twice +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) + } + headerMap[v] = true + } + return nil +} + +func readTo(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)); 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") + } + + headers := csvRows[0] + body := csvRows[1:] + + csvHeadersLabels := make(map[int]*fieldInfo, len(outInnerStructInfo.Fields)) // Used to store the correspondance header <-> position in CSV + + headerCount := map[string]int{} + for i, csvColumnHeader := range headers { + curHeaderCount := headerCount[csvColumnHeader] + if fieldInfo := getCSVFieldPosition(csvColumnHeader, outInnerStructInfo, curHeaderCount); fieldInfo != nil { + csvHeadersLabels[i] = fieldInfo + if ShouldAlignDuplicateHeadersWithStructFieldOrder { + curHeaderCount++ + headerCount[csvColumnHeader] = curHeaderCount + } + } + } + + if FailIfUnmatchedStructTags { + if err := maybeMissingStructFields(outInnerStructInfo.Fields, headers); err != nil { + return err + } + } + if FailIfDoubleHeaderNames { + if err := maybeDoubleHeaderNames(headers); err != nil { + return err + } + } + + for i, csvRow := range body { + outInner := createNewOutInner(outInnerWasPointer, outInnerType) + for j, csvColumnContent := range csvRow { + if fieldInfo, ok := csvHeadersLabels[j]; ok { // Position found accordingly to header name + 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.Index(i).Set(outInner) + } + return nil +} + +func readEach(decoder SimpleDecoder, c interface{}) error { + 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() + 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") + } + csvHeadersLabels := make(map[int]*fieldInfo, len(outInnerStructInfo.Fields)) // Used to store the correspondance header <-> position in CSV + headerCount := map[string]int{} + for i, csvColumnHeader := range headers { + curHeaderCount := headerCount[csvColumnHeader] + if fieldInfo := getCSVFieldPosition(csvColumnHeader, outInnerStructInfo, curHeaderCount); fieldInfo != nil { + csvHeadersLabels[i] = fieldInfo + if ShouldAlignDuplicateHeadersWithStructFieldOrder { + curHeaderCount++ + headerCount[csvColumnHeader] = curHeaderCount + } + } + } + if err := maybeMissingStructFields(outInnerStructInfo.Fields, headers); err != nil { + if FailIfUnmatchedStructTags { + return err + } + } + if FailIfDoubleHeaderNames { + if err := maybeDoubleHeaderNames(headers); err != nil { + return err + } + } + 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 { + if fieldInfo, ok := csvHeadersLabels[j]; ok { // Position found accordingly to header name + 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 +} + +// Check if the outType is an array or a slice +func ensureOutType(outType reflect.Type) error { + switch outType.Kind() { + case reflect.Slice: + fallthrough + case reflect.Chan: + fallthrough + case reflect.Array: + return nil + } + return fmt.Errorf("cannot use " + outType.String() + ", only slice or array supported") +} + +// Check if the outInnerType is of type struct +func ensureOutInnerType(outInnerType reflect.Type) error { + switch outInnerType.Kind() { + case reflect.Struct: + return nil + } + return fmt.Errorf("cannot use " + outInnerType.String() + ", only struct supported") +} + +func ensureOutCapacity(out *reflect.Value, csvLen int) error { + switch out.Kind() { + case reflect.Array: + if out.Len() < csvLen-1 { // Array is not big enough to hold the CSV content (arrays are not addressable) + return fmt.Errorf("array capacity problem: cannot store %d %s in %s", csvLen-1, out.Type().Elem().String(), out.Type().String()) + } + case reflect.Slice: + if !out.CanAddr() && out.Len() < csvLen-1 { // Slice is not big enough tho hold the CSV content and is not addressable + return fmt.Errorf("slice capacity problem and is not addressable (did you forget &?)") + } else if out.CanAddr() && out.Len() < csvLen-1 { + out.Set(reflect.MakeSlice(out.Type(), csvLen-1, csvLen-1)) // Slice is not big enough, so grows it + } + } + return nil +} + +func getCSVFieldPosition(key string, structInfo *structInfo, curHeaderCount int) *fieldInfo { + matchedFieldCount := 0 + for _, field := range structInfo.Fields { + if field.matchesKey(key) { + if matchedFieldCount >= curHeaderCount { + return &field + } else { + matchedFieldCount++ + } + } + } + return nil +} + +func createNewOutInner(outInnerWasPointer bool, outInnerType reflect.Type) reflect.Value { + if outInnerWasPointer { + return reflect.New(outInnerType) + } + return reflect.New(outInnerType).Elem() +} + +func setInnerField(outInner *reflect.Value, outInnerWasPointer bool, index []int, value string, omitEmpty bool) error { + oi := *outInner + if outInnerWasPointer { + oi = outInner.Elem() + } + return setField(oi.FieldByIndex(index), value, omitEmpty) +} diff --git a/vendor/github.com/gocarina/gocsv/encode.go b/vendor/github.com/gocarina/gocsv/encode.go new file mode 100644 index 0000000..7a41f9f --- /dev/null +++ b/vendor/github.com/gocarina/gocsv/encode.go @@ -0,0 +1,139 @@ +package gocsv + +import ( + "fmt" + "io" + "reflect" +) + +type encoder struct { + out io.Writer +} + +func newEncoder(out io.Writer) *encoder { + return &encoder{out} +} + +func writeFromChan(writer *SafeCSVWriter, c <-chan interface{}) error { + // Get the first value. It wil determine the header structure. + firstValue, ok := <-c + if !ok { + return fmt.Errorf("channel is closed") + } + inValue, inType := getConcreteReflectValueAndType(firstValue) // Get the concrete type + if err := ensureStructOrPtr(inType); err != nil { + return err + } + inInnerWasPointer := inType.Kind() == reflect.Ptr + inInnerStructInfo := getStructInfo(inType) // Get the inner struct info to get CSV annotations + csvHeadersLabels := make([]string, len(inInnerStructInfo.Fields)) + for i, fieldInfo := range inInnerStructInfo.Fields { // Used to write the header (first line) in CSV + csvHeadersLabels[i] = fieldInfo.getFirstKey() + } + if err := writer.Write(csvHeadersLabels); err != nil { + return err + } + write := func(val reflect.Value) error { + for j, fieldInfo := range inInnerStructInfo.Fields { + csvHeadersLabels[j] = "" + inInnerFieldValue, err := getInnerField(val, inInnerWasPointer, fieldInfo.IndexChain) // Get the correct field header <-> position + if err != nil { + return err + } + csvHeadersLabels[j] = inInnerFieldValue + } + if err := writer.Write(csvHeadersLabels); err != nil { + return err + } + return nil + } + if err := write(inValue); err != nil { + return err + } + for v := range c { + val, _ := getConcreteReflectValueAndType(v) // Get the concrete type (not pointer) (Slice or Array) + if err := ensureStructOrPtr(inType); err != nil { + return err + } + if err := write(val); err != nil { + return err + } + } + writer.Flush() + return writer.Error() +} + +func writeTo(writer *SafeCSVWriter, in interface{}, omitHeaders bool) error { + inValue, inType := getConcreteReflectValueAndType(in) // Get the concrete type (not pointer) (Slice or Array) + if err := ensureInType(inType); err != nil { + return err + } + inInnerWasPointer, inInnerType := getConcreteContainerInnerType(inType) // Get the concrete inner type (not pointer) (Container<"?">) + if err := ensureInInnerType(inInnerType); err != nil { + return err + } + inInnerStructInfo := getStructInfo(inInnerType) // Get the inner struct info to get CSV annotations + csvHeadersLabels := make([]string, len(inInnerStructInfo.Fields)) + for i, fieldInfo := range inInnerStructInfo.Fields { // Used to write the header (first line) in CSV + csvHeadersLabels[i] = fieldInfo.getFirstKey() + } + if !omitHeaders { + if err := writer.Write(csvHeadersLabels); err != nil { + return err + } + } + inLen := inValue.Len() + for i := 0; i < inLen; i++ { // Iterate over container rows + for j, fieldInfo := range inInnerStructInfo.Fields { + csvHeadersLabels[j] = "" + inInnerFieldValue, err := getInnerField(inValue.Index(i), inInnerWasPointer, fieldInfo.IndexChain) // Get the correct field header <-> position + if err != nil { + return err + } + csvHeadersLabels[j] = inInnerFieldValue + } + if err := writer.Write(csvHeadersLabels); err != nil { + return err + } + } + writer.Flush() + return writer.Error() +} + +func ensureStructOrPtr(t reflect.Type) error { + switch t.Kind() { + case reflect.Struct: + fallthrough + case reflect.Ptr: + return nil + } + return fmt.Errorf("cannot use " + t.String() + ", only slice or array supported") +} + +// Check if the inType is an array or a slice +func ensureInType(outType reflect.Type) error { + switch outType.Kind() { + case reflect.Slice: + fallthrough + case reflect.Array: + return nil + } + return fmt.Errorf("cannot use " + outType.String() + ", only slice or array supported") +} + +// Check if the inInnerType is of type struct +func ensureInInnerType(outInnerType reflect.Type) error { + switch outInnerType.Kind() { + case reflect.Struct: + return nil + } + return fmt.Errorf("cannot use " + outInnerType.String() + ", only struct supported") +} + +func getInnerField(outInner reflect.Value, outInnerWasPointer bool, index []int) (string, error) { + oi := outInner + if outInnerWasPointer { + oi = outInner.Elem() + } + return getFieldAsString(oi.FieldByIndex(index)) +} diff --git a/vendor/github.com/gocarina/gocsv/reflect.go b/vendor/github.com/gocarina/gocsv/reflect.go new file mode 100644 index 0000000..9217e30 --- /dev/null +++ b/vendor/github.com/gocarina/gocsv/reflect.go @@ -0,0 +1,107 @@ +package gocsv + +import ( + "reflect" + "strings" + "sync" +) + +// -------------------------------------------------------------------------- +// Reflection helpers + +type structInfo struct { + Fields []fieldInfo +} + +// fieldInfo is a struct field that should be mapped to a CSV column, or vice-versa +// Each IndexChain element before the last is the index of an the embedded struct field +// that defines Key as a tag +type fieldInfo struct { + keys []string + omitEmpty bool + IndexChain []int +} + +func (f fieldInfo) getFirstKey() string { + return f.keys[0] +} + +func (f fieldInfo) matchesKey(key string) bool { + for _, k := range f.keys { + if key == k || strings.TrimSpace(key) == k { + return true + } + } + return false +} + +var structMap = make(map[reflect.Type]*structInfo) +var structMapMutex sync.RWMutex + +func getStructInfo(rType reflect.Type) *structInfo { + structMapMutex.RLock() + stInfo, ok := structMap[rType] + structMapMutex.RUnlock() + if ok { + return stInfo + } + fieldsList := getFieldInfos(rType, []int{}) + stInfo = &structInfo{fieldsList} + return stInfo +} + +func getFieldInfos(rType reflect.Type, parentIndexChain []int) []fieldInfo { + fieldsCount := rType.NumField() + fieldsList := make([]fieldInfo, 0, fieldsCount) + for i := 0; i < fieldsCount; i++ { + field := rType.Field(i) + if field.PkgPath != "" { + continue + } + indexChain := append(parentIndexChain, i) + // if the field is an embedded struct, create a fieldInfo for each of its fields + if field.Anonymous && field.Type.Kind() == reflect.Struct { + fieldsList = append(fieldsList, getFieldInfos(field.Type, indexChain)...) + continue + } + fieldInfo := fieldInfo{IndexChain: indexChain} + fieldTag := field.Tag.Get("csv") + fieldTags := strings.Split(fieldTag, TagSeparator) + filteredTags := []string{} + for _, fieldTagEntry := range fieldTags { + if fieldTagEntry != "omitempty" { + filteredTags = append(filteredTags, fieldTagEntry) + } else { + fieldInfo.omitEmpty = true + } + } + + if len(filteredTags) == 1 && filteredTags[0] == "-" { + continue + } else if len(filteredTags) > 0 && filteredTags[0] != "" { + fieldInfo.keys = filteredTags + } else { + fieldInfo.keys = []string{field.Name} + } + fieldsList = append(fieldsList, fieldInfo) + } + return fieldsList +} + +func getConcreteContainerInnerType(in reflect.Type) (inInnerWasPointer bool, inInnerType reflect.Type) { + inInnerType = in.Elem() + inInnerWasPointer = false + if inInnerType.Kind() == reflect.Ptr { + inInnerWasPointer = true + inInnerType = inInnerType.Elem() + } + return inInnerWasPointer, inInnerType +} + +func getConcreteReflectValueAndType(in interface{}) (reflect.Value, reflect.Type) { + value := reflect.ValueOf(in) + if value.Kind() == reflect.Ptr { + value = value.Elem() + } + return value, value.Type() +} diff --git a/vendor/github.com/gocarina/gocsv/safe_csv.go b/vendor/github.com/gocarina/gocsv/safe_csv.go new file mode 100644 index 0000000..4b2882f --- /dev/null +++ b/vendor/github.com/gocarina/gocsv/safe_csv.go @@ -0,0 +1,32 @@ +package gocsv + +//Wraps around SafeCSVWriter and makes it thread safe. +import ( + "encoding/csv" + "sync" +) + +type SafeCSVWriter struct { + *csv.Writer + m sync.Mutex +} + +func NewSafeCSVWriter(original *csv.Writer) *SafeCSVWriter { + return &SafeCSVWriter{ + Writer: original, + } +} + +//Override write +func (w *SafeCSVWriter) Write(row []string) error { + w.m.Lock() + defer w.m.Unlock() + return w.Writer.Write(row) +} + +//Override flush +func (w *SafeCSVWriter) Flush() { + w.m.Lock() + w.Writer.Flush() + w.m.Unlock() +} diff --git a/vendor/github.com/gocarina/gocsv/types.go b/vendor/github.com/gocarina/gocsv/types.go new file mode 100644 index 0000000..50d88ce --- /dev/null +++ b/vendor/github.com/gocarina/gocsv/types.go @@ -0,0 +1,456 @@ +package gocsv + +import ( + "encoding" + "fmt" + "reflect" + "strconv" + "strings" +) + +// -------------------------------------------------------------------------- +// Conversion interfaces + +// TypeMarshaller is implemented by any value that has a MarshalCSV method +// This converter is used to convert the value to it string representation +type TypeMarshaller interface { + MarshalCSV() (string, error) +} + +// Stringer is implemented by any value that has a String method +// This converter is used to convert the value to it string representation +// This converter will be used if your value does not implement TypeMarshaller +type Stringer interface { + String() string +} + +// TypeUnmarshaller is implemented by any value that has an UnmarshalCSV method +// This converter is used to convert a string to your value representation of that string +type TypeUnmarshaller interface { + UnmarshalCSV(string) error +} + +// NoUnmarshalFuncError is the custom error type to be raised in case there is no unmarshal function defined on type +type NoUnmarshalFuncError struct { + msg string +} + +func (e NoUnmarshalFuncError) Error() string { + return e.msg +} + +// NoMarshalFuncError is the custom error type to be raised in case there is no marshal function defined on type +type NoMarshalFuncError struct { + msg string +} + +func (e NoMarshalFuncError) Error() string { + return e.msg +} + +var ( + stringerType = reflect.TypeOf((*Stringer)(nil)).Elem() + marshallerType = reflect.TypeOf((*TypeMarshaller)(nil)).Elem() + unMarshallerType = reflect.TypeOf((*TypeUnmarshaller)(nil)).Elem() + textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() + textUnMarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() +) + +// -------------------------------------------------------------------------- +// Conversion helpers + +func toString(in interface{}) (string, error) { + inValue := reflect.ValueOf(in) + + switch inValue.Kind() { + case reflect.String: + return inValue.String(), nil + case reflect.Bool: + b := inValue.Bool() + if b { + return "true", nil + } + return "false", nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return fmt.Sprintf("%v", inValue.Int()), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return fmt.Sprintf("%v", inValue.Uint()), nil + case reflect.Float32: + return strconv.FormatFloat(inValue.Float(), byte('f'), -1, 32), nil + case reflect.Float64: + return strconv.FormatFloat(inValue.Float(), byte('f'), -1, 64), nil + } + return "", fmt.Errorf("No known conversion from " + inValue.Type().String() + " to string") +} + +func toBool(in interface{}) (bool, error) { + inValue := reflect.ValueOf(in) + + switch inValue.Kind() { + case reflect.String: + s := inValue.String() + switch s { + case "yes": + return true, nil + case "no", "": + return false, nil + default: + return strconv.ParseBool(s) + } + case reflect.Bool: + return inValue.Bool(), nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + i := inValue.Int() + if i != 0 { + return true, nil + } + return false, nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + i := inValue.Uint() + if i != 0 { + return true, nil + } + return false, nil + case reflect.Float32, reflect.Float64: + f := inValue.Float() + if f != 0 { + return true, nil + } + return false, nil + } + return false, fmt.Errorf("No known conversion from " + inValue.Type().String() + " to bool") +} + +func toInt(in interface{}) (int64, error) { + inValue := reflect.ValueOf(in) + + switch inValue.Kind() { + case reflect.String: + s := strings.TrimSpace(inValue.String()) + if s == "" { + return 0, nil + } + return strconv.ParseInt(s, 0, 64) + case reflect.Bool: + if inValue.Bool() { + return 1, nil + } + return 0, nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return inValue.Int(), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return int64(inValue.Uint()), nil + case reflect.Float32, reflect.Float64: + return int64(inValue.Float()), nil + } + return 0, fmt.Errorf("No known conversion from " + inValue.Type().String() + " to int") +} + +func toUint(in interface{}) (uint64, error) { + inValue := reflect.ValueOf(in) + + switch inValue.Kind() { + case reflect.String: + s := strings.TrimSpace(inValue.String()) + if s == "" { + return 0, nil + } + + // support the float input + if strings.Contains(s, ".") { + f, err := strconv.ParseFloat(s, 64) + if err != nil { + return 0, err + } + return uint64(f), nil + } + return strconv.ParseUint(s, 0, 64) + case reflect.Bool: + if inValue.Bool() { + return 1, nil + } + return 0, nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return uint64(inValue.Int()), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return inValue.Uint(), nil + case reflect.Float32, reflect.Float64: + return uint64(inValue.Float()), nil + } + return 0, fmt.Errorf("No known conversion from " + inValue.Type().String() + " to uint") +} + +func toFloat(in interface{}) (float64, error) { + inValue := reflect.ValueOf(in) + + switch inValue.Kind() { + case reflect.String: + s := strings.TrimSpace(inValue.String()) + if s == "" { + return 0, nil + } + return strconv.ParseFloat(s, 64) + case reflect.Bool: + if inValue.Bool() { + return 1, nil + } + return 0, nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return float64(inValue.Int()), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return float64(inValue.Uint()), nil + case reflect.Float32, reflect.Float64: + return inValue.Float(), nil + } + return 0, fmt.Errorf("No known conversion from " + inValue.Type().String() + " to float") +} + +func setField(field reflect.Value, value string, omitEmpty bool) error { + if field.Kind() == reflect.Ptr { + if omitEmpty && value == "" { + return nil + } + if field.IsNil() { + field.Set(reflect.New(field.Type().Elem())) + } + field = field.Elem() + } + + switch field.Interface().(type) { + case string: + s, err := toString(value) + if err != nil { + return err + } + field.SetString(s) + case bool: + b, err := toBool(value) + if err != nil { + return err + } + field.SetBool(b) + case int, int8, int16, int32, int64: + i, err := toInt(value) + if err != nil { + return err + } + field.SetInt(i) + case uint, uint8, uint16, uint32, uint64: + ui, err := toUint(value) + if err != nil { + return err + } + field.SetUint(ui) + case float32, float64: + f, err := toFloat(value) + if err != nil { + return err + } + field.SetFloat(f) + default: + // Not a native type, check for unmarshal method + if err := unmarshall(field, value); err != nil { + if _, ok := err.(NoUnmarshalFuncError); !ok { + return err + } + // Could not unmarshal, check for kind, e.g. renamed type from basic type + switch field.Kind() { + case reflect.String: + s, err := toString(value) + if err != nil { + return err + } + field.SetString(s) + case reflect.Bool: + b, err := toBool(value) + if err != nil { + return err + } + field.SetBool(b) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + i, err := toInt(value) + if err != nil { + return err + } + field.SetInt(i) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + ui, err := toUint(value) + if err != nil { + return err + } + field.SetUint(ui) + case reflect.Float32, reflect.Float64: + f, err := toFloat(value) + if err != nil { + return err + } + field.SetFloat(f) + default: + return err + } + } else { + return nil + } + } + return nil +} + +func getFieldAsString(field reflect.Value) (str string, err error) { + switch field.Kind() { + case reflect.Interface: + case reflect.Ptr: + if field.IsNil() { + return "", nil + } + return getFieldAsString(field.Elem()) + default: + // Check if field is go native type + switch field.Interface().(type) { + case string: + return field.String(), nil + case bool: + str, err = toString(field.Bool()) + if err != nil { + return str, err + } + case int, int8, int16, int32, int64: + str, err = toString(field.Int()) + if err != nil { + return str, err + } + case uint, uint8, uint16, uint32, uint64: + str, err = toString(field.Uint()) + if err != nil { + return str, err + } + case float32: + str, err = toString(float32(field.Float())) + if err != nil { + return str, err + } + case float64: + str, err = toString(field.Float()) + if err != nil { + return str, err + } + default: + // Not a native type, check for marshal method + str, err = marshall(field) + if err != nil { + if _, ok := err.(NoMarshalFuncError); !ok { + return str, err + } + // If not marshal method, is field compatible with/renamed from native type + switch field.Kind() { + case reflect.String: + return field.String(), nil + case reflect.Bool: + str, err = toString(field.Bool()) + if err != nil { + return str, err + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + str, err = toString(field.Int()) + if err != nil { + return str, err + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + str, err = toString(field.Uint()) + if err != nil { + return str, err + } + case reflect.Float32: + str, err = toString(float32(field.Float())) + if err != nil { + return str, err + } + case reflect.Float64: + str, err = toString(field.Float()) + if err != nil { + return str, err + } + } + } else { + return str, nil + } + } + } + return str, nil +} + +// -------------------------------------------------------------------------- +// Un/serializations helpers + +func unmarshall(field reflect.Value, value string) error { + dupField := field + unMarshallIt := func(finalField reflect.Value) error { + if finalField.CanInterface() { + fieldIface := finalField.Interface() + + fieldTypeUnmarshaller, ok := fieldIface.(TypeUnmarshaller) + if ok { + return fieldTypeUnmarshaller.UnmarshalCSV(value) + } + + // Otherwise try to use TextUnmarshaler + fieldTextUnmarshaler, ok := fieldIface.(encoding.TextUnmarshaler) + if ok { + return fieldTextUnmarshaler.UnmarshalText([]byte(value)) + } + } + + return NoUnmarshalFuncError{"No known conversion from string to " + field.Type().String() + ", " + field.Type().String() + " does not implement TypeUnmarshaller"} + } + for dupField.Kind() == reflect.Interface || dupField.Kind() == reflect.Ptr { + if dupField.IsNil() { + dupField = reflect.New(field.Type().Elem()) + field.Set(dupField) + return unMarshallIt(dupField) + } + dupField = dupField.Elem() + } + if dupField.CanAddr() { + return unMarshallIt(dupField.Addr()) + } + return NoUnmarshalFuncError{"No known conversion from string to " + field.Type().String() + ", " + field.Type().String() + " does not implement TypeUnmarshaller"} +} + +func marshall(field reflect.Value) (value string, err error) { + dupField := field + marshallIt := func(finalField reflect.Value) (string, error) { + if finalField.CanInterface() { + fieldIface := finalField.Interface() + + // Use TypeMarshaller when possible + fieldTypeMarhaller, ok := fieldIface.(TypeMarshaller) + if ok { + return fieldTypeMarhaller.MarshalCSV() + } + + // Otherwise try to use TextMarshaller + fieldTextMarshaler, ok := fieldIface.(encoding.TextMarshaler) + if ok { + text, err := fieldTextMarshaler.MarshalText() + return string(text), err + } + + // Otherwise try to use Stringer + fieldStringer, ok := fieldIface.(Stringer) + if ok { + return fieldStringer.String(), nil + } + } + + return value, NoMarshalFuncError{"No known conversion from " + field.Type().String() + " to string, " + field.Type().String() + " does not implement TypeMarshaller nor Stringer"} + } + for dupField.Kind() == reflect.Interface || dupField.Kind() == reflect.Ptr { + if dupField.IsNil() { + return value, nil + } + dupField = dupField.Elem() + } + if dupField.CanAddr() { + return marshallIt(dupField.Addr()) + } + return value, NoMarshalFuncError{"No known conversion from " + field.Type().String() + " to string, " + field.Type().String() + " does not implement TypeMarshaller nor Stringer"} +} diff --git a/vendor/github.com/gocarina/gocsv/unmarshaller.go b/vendor/github.com/gocarina/gocsv/unmarshaller.go new file mode 100644 index 0000000..ace601e --- /dev/null +++ b/vendor/github.com/gocarina/gocsv/unmarshaller.go @@ -0,0 +1,97 @@ +package gocsv + +import ( + "encoding/csv" + "errors" + "fmt" + "reflect" +) + +// Unmarshaller is a CSV to struct unmarshaller. +type Unmarshaller struct { + reader *csv.Reader + fieldInfoMap map[int]*fieldInfo + MismatchedHeaders []string + MismatchedStructFields []string + outType reflect.Type +} + +// NewUnmarshaller creates an unmarshaller from a csv.Reader and a struct. +func NewUnmarshaller(reader *csv.Reader, out interface{}) (*Unmarshaller, error) { + headers, err := reader.Read() + if err != nil { + return nil, err + } + + um := &Unmarshaller{reader: reader, outType: reflect.TypeOf(out)} + err = validate(um, out, headers) + if err != nil { + return nil, err + } + return um, nil +} + +// Read returns an interface{} whose runtime type is the same as the struct that +// was used to create the Unmarshaller. +func (um *Unmarshaller) Read() (interface{}, error) { + row, err := um.reader.Read() + if err != nil { + return nil, err + } + return um.unmarshalRow(row) +} + +// validate ensures that a struct was used to create the Unmarshaller, and validates +// CSV headers against the CSV tags in the struct. +func validate(um *Unmarshaller, s interface{}, headers []string) error { + concreteType := reflect.TypeOf(s) + if concreteType.Kind() == reflect.Ptr { + concreteType = concreteType.Elem() + } + if err := ensureOutInnerType(concreteType); err != nil { + return err + } + structInfo := getStructInfo(concreteType) // Get struct info to get CSV annotations. + if len(structInfo.Fields) == 0 { + return errors.New("no csv struct tags found") + } + csvHeadersLabels := make(map[int]*fieldInfo, len(structInfo.Fields)) // Used to store the corresponding header <-> position in CSV + headerCount := map[string]int{} + for i, csvColumnHeader := range headers { + curHeaderCount := headerCount[csvColumnHeader] + if fieldInfo := getCSVFieldPosition(csvColumnHeader, structInfo, curHeaderCount); fieldInfo != nil { + csvHeadersLabels[i] = fieldInfo + if ShouldAlignDuplicateHeadersWithStructFieldOrder { + curHeaderCount++ + headerCount[csvColumnHeader] = curHeaderCount + } + } + } + if err := maybeDoubleHeaderNames(headers); err != nil { + return err + } + + um.fieldInfoMap = csvHeadersLabels + um.MismatchedHeaders = mismatchHeaderFields(structInfo.Fields, headers) + um.MismatchedStructFields = mismatchStructFields(structInfo.Fields, headers) + return nil +} + +// unmarshalRow converts a CSV row to a struct, based on CSV struct tags. +func (um *Unmarshaller) unmarshalRow(row []string) (interface{}, error) { + isPointer := false + concreteOutType := um.outType + if um.outType.Kind() == reflect.Ptr { + isPointer = true + concreteOutType = concreteOutType.Elem() + } + outValue := createNewOutInner(isPointer, concreteOutType) + for j, csvColumnContent := range row { + if fieldInfo, ok := um.fieldInfoMap[j]; ok { + if err := setInnerField(&outValue, isPointer, fieldInfo.IndexChain, csvColumnContent, fieldInfo.omitEmpty); err != nil { // Set field of struct + return nil, fmt.Errorf("cannot assign field at %v to %s through index chain %v: %v", j, outValue.Type(), fieldInfo.IndexChain, err) + } + } + } + return outValue.Interface(), nil +} -- cgit v1.2.3