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 {
handlersByName map[string]reflect.StructField
outType reflect.Type
fieldsHandlers []FieldsHandler
fieldByTag map[string]columnFieldBinding
outType reflect.Type
columnNameMapped bool
}
type columnFieldBinding struct {
field reflect.StructField
index int
}
func newDefaultHandler() *defaultRecordHandler {
newHander := new(defaultRecordHandler)
newHander.handlersByName = make(map[string]reflect.StructField)
return newHander
newHandler := new(defaultRecordHandler)
newHandler.fieldByTag = make(map[string]columnFieldBinding)
newHandler.columnNameMapped = false
return newHandler
}
func (d *defaultRecordHandler) HandleRecord(v interface{}, record []string) error {
//TODO: implementation
func (d *defaultRecordHandler) HandleRecord(v any, record []string) error {
var (
err error
)
if d.outType == nil {
err = d.detType(v)
if err != 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 {
}
return nil
}
func (d *defaultRecordHandler) FieldsHandlers() []FieldsHandler {
//TODO: implementation
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)
if typ.Kind() == reflect.Pointer {
typ = typ.Elem()
if typ.Kind() == reflect.Struct {
d.outType = typ
return d.buildStructHandlers()
return d.mapStructTag()
} else if typ.Kind() == reflect.Map {
//TODO: implementation
} 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)
}
func (d *defaultRecordHandler) buildStructHandlers() error {
func (d *defaultRecordHandler) mapStructTag() error {
//TODO: implementation
for _, field := range reflect.VisibleFields(d.outType) {
if csv, ok := field.Tag.Lookup(csvTag); ok {
@ -60,10 +77,12 @@ func (d *defaultRecordHandler) buildStructHandlers() error {
if len(s) == 0 {
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])
}
d.handlersByName[s[0]] = field
d.fieldByTag[s[0]] = columnFieldBinding{
field: field,
}
} else if csvIndex, ok := field.Tag.Lookup(csvIndexTag); ok {
_ = 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
import (
"bytes"
"encoding/csv"
)
@ -26,49 +27,116 @@ type FieldsHandler interface {
NumFields() int
}
type RecordHandler interface {
type RecordFieldsHandler interface {
FieldsHandlers() []FieldsHandler
Out(v any) error
}
type RecordHandler interface {
// SetFieldConfigs is only effective if a Map is passed to HandleRecord
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 {
header []string
reader *csv.Reader
recordHandler RecordHandler
options []CsvOption
header []string
recordHandler RecordHandler
recordFieldHandler RecordFieldsHandler
handlerType recordHandlerType
csvReader *csv.Reader
}
func NewUnmarshaler() *Unmarshaler {
return &Unmarshaler{}
}
func (u *Unmarshaler) WithCsvReader(reader *csv.Reader) *Unmarshaler {
u.reader = reader
return u
}
func (u *Unmarshaler) WithHeader(header []string) *Unmarshaler {
if len(header) > 0 {
newHeader := make([]string, len(header))
copy(newHeader, header)
u.header = newHeader
func NewUnmarshaler(options ...CsvOption) *Unmarshaler {
unmarshaler := &Unmarshaler{
handlerType: direct,
options: []CsvOption{nil},
}
return u
if len(options) > 0 {
unmarshaler.options = append(unmarshaler.options, options...)
}
return unmarshaler
}
func (u *Unmarshaler) WithRecordHandler(handler RecordHandler) *Unmarshaler {
u.recordHandler = handler
return u
func (u *Unmarshaler) processOptions(options ...CsvOption) {
for _, option := range options {
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 interface{}) error {
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 {
}
//TODO: implementation
return nil
}
func Unmarshal(data []byte, v interface{}) error {
return NewUnmarshaler().Unmarshal(data, v)
func Unmarshal(data []byte, v any, options ...CsvOption) error {
return NewUnmarshaler().Unmarshal(data, v, options...)
}
// func Read(i interface{}) {

View File

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