170 lines
4.6 KiB
Go
170 lines
4.6 KiB
Go
package gocsv
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
)
|
|
|
|
var (
|
|
ErrChannelIsClosed = errors.New("channel is closed")
|
|
)
|
|
|
|
type encoder struct {
|
|
out io.Writer
|
|
}
|
|
|
|
func newEncoder(out io.Writer) *encoder {
|
|
return &encoder{out}
|
|
}
|
|
|
|
func writeFromChan(writer CSVWriter, c <-chan interface{}, omitHeaders bool) error {
|
|
// Get the first value. It wil determine the header structure.
|
|
firstValue, ok := <-c
|
|
if !ok {
|
|
return ErrChannelIsClosed
|
|
}
|
|
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 !omitHeaders {
|
|
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 CSVWriter, 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 {
|
|
if oi.IsNil() {
|
|
return "", nil
|
|
}
|
|
oi = outInner.Elem()
|
|
}
|
|
|
|
if oi.Kind() == reflect.Slice || oi.Kind() == reflect.Array {
|
|
i := index[0]
|
|
|
|
if i >= oi.Len() {
|
|
return "", nil
|
|
}
|
|
|
|
item := oi.Index(i)
|
|
if len(index) > 1 {
|
|
return getInnerField(item, false, index[1:])
|
|
}
|
|
return getFieldAsString(item)
|
|
}
|
|
|
|
// 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 getInnerField(nextField, nextField.Kind() == reflect.Ptr, index[1:])
|
|
}
|
|
return getFieldAsString(oi.FieldByIndex(index))
|
|
}
|