wip: unmarshal
This commit is contained in:
parent
518d723a8a
commit
5b9b896f34
@ -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
1
example/example_test.go
Normal file
@ -0,0 +1 @@
|
||||
package example
|
||||
1
example/mtcars.go
Normal file
1
example/mtcars.go
Normal file
@ -0,0 +1 @@
|
||||
package example
|
||||
25
ext_test.go
Normal file
25
ext_test.go
Normal 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
178
options.go
Normal 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
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
# Proposal 1
|
||||
|
||||
120
unmarshal.go
120
unmarshal.go
@ -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{}) {
|
||||
|
||||
@ -18,7 +18,7 @@ type anon struct {
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
newDefaultHandler().parseVal(&Coba{})
|
||||
newDefaultHandler().detType(&Coba{})
|
||||
}
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user