wip: unmarshal

This commit is contained in:
Suyono 2023-06-25 13:41:36 +10:00
parent 518d723a8a
commit 5b9b896f34
8 changed files with 336 additions and 46 deletions

View File

@ -7,27 +7,44 @@ import (
) )
type defaultRecordHandler struct { type defaultRecordHandler struct {
handlersByName map[string]reflect.StructField fieldByTag map[string]columnFieldBinding
outType reflect.Type outType reflect.Type
fieldsHandlers []FieldsHandler columnNameMapped bool
}
type columnFieldBinding struct {
field reflect.StructField
index int
} }
func newDefaultHandler() *defaultRecordHandler { func newDefaultHandler() *defaultRecordHandler {
newHander := new(defaultRecordHandler) newHandler := new(defaultRecordHandler)
newHander.handlersByName = make(map[string]reflect.StructField) newHandler.fieldByTag = make(map[string]columnFieldBinding)
return newHander newHandler.columnNameMapped = false
return newHandler
} }
func (d *defaultRecordHandler) HandleRecord(v interface{}, record []string) error { func (d *defaultRecordHandler) HandleRecord(v any, record []string) error {
//TODO: implementation var (
err error
)
if d.outType == nil { if d.outType == nil {
err = d.detType(v)
} if err != nil {
return nil return fmt.Errorf("error HandleRecord: detType: %+v", err)
}
err = d.mapStructTag()
if err != nil {
return fmt.Errorf("error HandleRecord: mapStructTag: %+v", err)
}
}
if !d.columnNameMapped {
} }
func (d *defaultRecordHandler) FieldsHandlers() []FieldsHandler {
//TODO: implementation
return nil return nil
} }
@ -35,14 +52,14 @@ func (d *defaultRecordHandler) SetFieldConfigs(configs []FieldsConfig) {
} }
func (d *defaultRecordHandler) parseVal(v interface{}) error { func (d *defaultRecordHandler) detType(v any) error {
typ := reflect.TypeOf(v) typ := reflect.TypeOf(v)
if typ.Kind() == reflect.Pointer { if typ.Kind() == reflect.Pointer {
typ = typ.Elem() typ = typ.Elem()
if typ.Kind() == reflect.Struct { if typ.Kind() == reflect.Struct {
d.outType = typ d.outType = typ
return d.buildStructHandlers() return d.mapStructTag()
} else if typ.Kind() == reflect.Map { } else if typ.Kind() == reflect.Map {
//TODO: implementation //TODO: implementation
} else if typ.Kind() == reflect.Slice { } else if typ.Kind() == reflect.Slice {
@ -52,7 +69,7 @@ func (d *defaultRecordHandler) parseVal(v interface{}) error {
return fmt.Errorf("v should be pointer of Struct, Map, or Slice: %+v", typ) return fmt.Errorf("v should be pointer of Struct, Map, or Slice: %+v", typ)
} }
func (d *defaultRecordHandler) buildStructHandlers() error { func (d *defaultRecordHandler) mapStructTag() error {
//TODO: implementation //TODO: implementation
for _, field := range reflect.VisibleFields(d.outType) { for _, field := range reflect.VisibleFields(d.outType) {
if csv, ok := field.Tag.Lookup(csvTag); ok { if csv, ok := field.Tag.Lookup(csvTag); ok {
@ -60,10 +77,12 @@ func (d *defaultRecordHandler) buildStructHandlers() error {
if len(s) == 0 { if len(s) == 0 {
return fmt.Errorf("invalid tag %+v", field.Tag) return fmt.Errorf("invalid tag %+v", field.Tag)
} }
if _, ok = d.handlersByName[s[0]]; ok { if _, ok = d.fieldByTag[s[0]]; ok {
return fmt.Errorf("problem with the receiving struct, multiple field with tag %s", s[0]) return fmt.Errorf("problem with the receiving struct, multiple field with tag %s", s[0])
} }
d.handlersByName[s[0]] = field d.fieldByTag[s[0]] = columnFieldBinding{
field: field,
}
} else if csvIndex, ok := field.Tag.Lookup(csvIndexTag); ok { } else if csvIndex, ok := field.Tag.Lookup(csvIndexTag); ok {
_ = csvIndex //TODO: process tag _ = csvIndex //TODO: process tag
} }

1
example/example_test.go Normal file
View File

@ -0,0 +1 @@
package example

1
example/mtcars.go Normal file
View File

@ -0,0 +1 @@
package example

25
ext_test.go Normal file
View File

@ -0,0 +1,25 @@
package gocsvparser_test
import (
"testing"
"github.com/budiuno/gocsvparser"
)
type testDirect struct {
}
func (t *testDirect) SetFieldConfigs(configs []gocsvparser.FieldsConfig) {
}
func (t *testDirect) HandleRecord(v any, records []string) error {
return nil
}
func TestExt(t *testing.T) {
// _, err := gocsvparser.NewUnmarshaler().WithRecordHandler(new(testDirect))
// if err != nil {
// t.Fatalf("unexpected error %v", err)
// }
}

178
options.go Normal file
View File

@ -0,0 +1,178 @@
package gocsvparser
import (
"encoding/csv"
"errors"
"fmt"
)
type csvOptionsType int
type CsvOption interface {
getType() csvOptionsType
}
const (
comma csvOptionsType = iota
comment
fieldsPerRecord
lazyQuotes
trimLeadingSpace
reuseRecord
useCrlf
columnHeader
recordHandler
csvReader
)
type csvReaderOption struct {
reader *csv.Reader
}
func CsvReader(reader *csv.Reader) csvReaderOption {
return csvReaderOption{
reader: reader,
}
}
func (csvReaderOption) getType() csvOptionsType {
return csvReader
}
type recordHandlerOption struct {
handlerType recordHandlerType
recordHandler RecordHandler
recordFieldsHandler RecordFieldsHandler
}
func RecordHandlerOption(handler any) (*recordHandlerOption, error) {
var rho *recordHandlerOption
if d, ok := handler.(RecordHandler); ok {
if d == nil {
return nil, errors.New("handler value is nil")
}
rho = &recordHandlerOption{
handlerType: direct,
recordHandler: d,
}
} else if s, ok := handler.(RecordFieldsHandler); ok {
if s == nil {
return nil, errors.New("handler value is nil")
}
rho = &recordHandlerOption{
handlerType: fieldsSpecific,
recordFieldsHandler: s,
}
} else {
return nil, fmt.Errorf("invalid handler type %T", handler)
}
return rho, nil
}
func (r *recordHandlerOption) getType() csvOptionsType {
return recordHandler
}
type columnHeaderOption struct {
header []string
}
func ColumnHeader(header ...string) columnHeaderOption {
columnHeader := columnHeaderOption{}
if len(header) > 0 {
columnHeader.header = make([]string, len(header))
copy(columnHeader.header, header)
}
return columnHeader
}
func (c columnHeaderOption) getType() csvOptionsType {
return columnHeader
}
type commaOption struct {
comma rune
}
func CommaOption(comma rune) commaOption {
return commaOption{
comma: comma,
}
}
func (c commaOption) getType() csvOptionsType {
return comma
}
type commentOption struct {
comment rune
}
func CommentOption(comment rune) commentOption {
return commentOption{
comment: comment,
}
}
func (c commentOption) getType() csvOptionsType {
return comment
}
type fieldsPerRecordOption struct {
fieldsPerRecord int
}
func FieldPerRecordOption(i int) fieldsPerRecordOption {
return fieldsPerRecordOption{
fieldsPerRecord: i,
}
}
func (f fieldsPerRecordOption) getType() csvOptionsType {
return fieldsPerRecord
}
type lazyQuotesOption struct {
lazyQuotes bool
}
func LazyQuotesOption(b bool) lazyQuotesOption {
return lazyQuotesOption{
lazyQuotes: b,
}
}
func (l lazyQuotesOption) getType() csvOptionsType {
return lazyQuotes
}
type trimLeadingSpaceOption struct {
trimLeadingSpace bool
}
func TrimLeadingSpaceOption(b bool) trimLeadingSpaceOption {
return trimLeadingSpaceOption{
trimLeadingSpace: b,
}
}
func (t trimLeadingSpaceOption) getType() csvOptionsType {
return trimLeadingSpace
}
type reuseRecordOption struct {
reuseRecord bool
}
func ReuseRecordOption(b bool) reuseRecordOption {
return reuseRecordOption{
reuseRecord: b,
}
}
func (r reuseRecordOption) getType() csvOptionsType {
return reuseRecord
}

View File

@ -1,2 +0,0 @@
# Proposal 1

View File

@ -1,6 +1,7 @@
package gocsvparser package gocsvparser
import ( import (
"bytes"
"encoding/csv" "encoding/csv"
) )
@ -26,49 +27,116 @@ type FieldsHandler interface {
NumFields() int NumFields() int
} }
type RecordHandler interface { type RecordFieldsHandler interface {
FieldsHandlers() []FieldsHandler FieldsHandlers() []FieldsHandler
Out(v any) error
}
type RecordHandler interface {
// SetFieldConfigs is only effective if a Map is passed to HandleRecord // SetFieldConfigs is only effective if a Map is passed to HandleRecord
SetFieldConfigs(configs []FieldsConfig) SetFieldConfigs(configs []FieldsConfig)
HandleRecord(v interface{}, record []string) error HandleRecord(v any, record []string) error
} }
type recordHandlerType int
const (
direct recordHandlerType = 1
fieldsSpecific recordHandlerType = 2
)
type Unmarshaler struct { type Unmarshaler struct {
options []CsvOption
header []string header []string
reader *csv.Reader
recordHandler RecordHandler recordHandler RecordHandler
recordFieldHandler RecordFieldsHandler
handlerType recordHandlerType
csvReader *csv.Reader
} }
func NewUnmarshaler() *Unmarshaler { func NewUnmarshaler(options ...CsvOption) *Unmarshaler {
return &Unmarshaler{} unmarshaler := &Unmarshaler{
handlerType: direct,
options: []CsvOption{nil},
} }
func (u *Unmarshaler) WithCsvReader(reader *csv.Reader) *Unmarshaler { if len(options) > 0 {
u.reader = reader unmarshaler.options = append(unmarshaler.options, options...)
return u
} }
func (u *Unmarshaler) WithHeader(header []string) *Unmarshaler { return unmarshaler
if len(header) > 0 {
newHeader := make([]string, len(header))
copy(newHeader, header)
u.header = newHeader
}
return u
} }
func (u *Unmarshaler) WithRecordHandler(handler RecordHandler) *Unmarshaler { func (u *Unmarshaler) processOptions(options ...CsvOption) {
u.recordHandler = handler for _, option := range options {
return u if option == nil {
continue
}
switch option.getType() {
case csvReader:
if u.csvReader != nil {
continue
}
o := option.(csvReaderOption)
u.csvReader = o.reader
case comma:
o := option.(commaOption)
u.csvReader.Comma = o.comma
case comment:
o := option.(commentOption)
u.csvReader.Comment = o.comment
case fieldsPerRecord:
o := option.(fieldsPerRecordOption)
u.csvReader.FieldsPerRecord = o.fieldsPerRecord
case lazyQuotes:
o := option.(lazyQuotesOption)
u.csvReader.LazyQuotes = o.lazyQuotes
case trimLeadingSpace:
o := option.(trimLeadingSpaceOption)
u.csvReader.TrimLeadingSpace = o.trimLeadingSpace
case reuseRecord:
o := option.(reuseRecordOption)
u.csvReader.ReuseRecord = o.reuseRecord
case columnHeader:
headerOption := option.(columnHeaderOption)
u.header = headerOption.header
case recordHandler:
rho := option.(*recordHandlerOption)
u.handlerType = rho.handlerType
switch rho.handlerType {
case direct:
u.recordHandler = rho.recordHandler
case fieldsSpecific:
u.recordFieldHandler = rho.recordFieldsHandler
}
}
}
}
func (u *Unmarshaler) Unmarshal(data []byte, v any, options ...CsvOption) error {
if len(options) > 0 {
u.options = append(u.options, options...)
}
u.options[0] = CsvReader(csv.NewReader(bytes.NewReader(data)))
u.processOptions(u.options...)
if u.handlerType == direct {
if u.recordHandler == nil {
//TODO: build default generator
}
} else {
} }
func (u *Unmarshaler) Unmarshal(data []byte, v interface{}) error {
//TODO: implementation //TODO: implementation
return nil return nil
} }
func Unmarshal(data []byte, v interface{}) error { func Unmarshal(data []byte, v any, options ...CsvOption) error {
return NewUnmarshaler().Unmarshal(data, v) return NewUnmarshaler().Unmarshal(data, v, options...)
} }
// func Read(i interface{}) { // func Read(i interface{}) {

View File

@ -18,7 +18,7 @@ type anon struct {
} }
func TestParse(t *testing.T) { func TestParse(t *testing.T) {
newDefaultHandler().parseVal(&Coba{}) newDefaultHandler().detType(&Coba{})
} }
func TestRead(t *testing.T) { func TestRead(t *testing.T) {