Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add From, To to XsvRead #13

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,21 @@ f,1,baz,,*string,*string`)
}
}

func Test_readTo_FromTo(t *testing.T) {
b := bytes.NewBufferString(`foo,BAR,Baz,Blah,SPtr,Omit
f,1,baz,,*string,*string
e,3,b,,,`)
var samples []Sample
xsvRead := NewXsvRead[Sample]()
xsvRead.To = 1
if err := xsvRead.SetReader(csv.NewReader(b)).ReadTo(&samples); err != nil {
t.Fatal(err)
}
if len(samples) != 1 {
t.Fatalf("expected 1 sample instances, got %d", len(samples))
}
}

func Test_readToNormalized(t *testing.T) {

blah := 0
Expand Down Expand Up @@ -393,6 +408,62 @@ aa,bb,11,cc,dd,ee`)
}
}

func Test_readEach_FromTo(t *testing.T) {
b := bytes.NewBufferString(`
first,foo,BAR,Baz,last,abc
aa,bb,11,cc,dd,ee
ff,gg,22,hh,ii,jj
kk,ll,33,mm,nn,oo
`)

c := make(chan SkipFieldSample)
var samples []SkipFieldSample
go func() {
xsvRead := NewXsvRead[SkipFieldSample]()
xsvRead.From = 1
xsvRead.To = 2
if err := xsvRead.SetReader(csv.NewReader(b)).ReadEach(c); err != nil {
t.Fatal(err)
}
}()
for v := range c {
samples = append(samples, v)
}
if len(samples) != 2 {
t.Fatalf("expected 2 sample instances, got %d", len(samples))
}
expected := SkipFieldSample{
EmbedSample: EmbedSample{
Qux: "aa",
Sample: Sample{
Foo: "bb",
Bar: 11,
Baz: "cc",
},
Quux: "dd",
},
Corge: "ee",
}
if expected != samples[0] {
t.Fatalf("expected first sample %v, got %v", expected, samples[0])
}
expected = SkipFieldSample{
EmbedSample: EmbedSample{
Qux: "ff",
Sample: Sample{
Foo: "gg",
Bar: 22,
Baz: "hh",
},
Quux: "ii",
},
Corge: "jj",
}
if expected != samples[1] {
t.Fatalf("expected first sample %v, got %v", expected, samples[1])
}
}

func Test_readEachWithoutHeaders(t *testing.T) {
blah := 0
sptr := ""
Expand Down Expand Up @@ -893,6 +964,43 @@ func TestCSVToMaps_OnRecord(t *testing.T) {
}
}

func TestCSVToMaps_FromTo(t *testing.T) {
b := bytes.NewBufferString(`foo,BAR,Baz
4,Jose,42
2,Daniel,21
5,Vincent,84`)
xsvRead := NewXsvRead[interface{}]()
xsvRead.From = 2
xsvRead.To = 3
m, err := xsvRead.SetReader(csv.NewReader(b)).ToMap()
if err != nil {
t.Fatal(err)
}
if len(m) != 2 {
t.Fatal("Expected 2 len, but", len(m))
}
firstRecord := m[0]
if firstRecord["foo"] != "2" {
t.Fatal("Expected 2 got", firstRecord["foo"])
}
if firstRecord["BAR"] != "Daniel" {
t.Fatal("Expected Daniel got", firstRecord["BAR"])
}
if firstRecord["Baz"] != "21" {
t.Fatal("Expected 21 got", firstRecord["Baz"])
}
secondRecord := m[1]
if secondRecord["foo"] != "5" {
t.Fatal("Expected 5 got", secondRecord["foo"])
}
if secondRecord["BAR"] != "Vincent" {
t.Fatal("Expected Vincent got", secondRecord["BAR"])
}
if secondRecord["Baz"] != "84" {
t.Fatal("Expected 84 got", secondRecord["Baz"])
}
}

func TestUnmarshalToDecoder(t *testing.T) {
blah := 0
sptr := "*string"
Expand Down
19 changes: 18 additions & 1 deletion examples/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package main

import (
"fmt"
"github.com/shigetaichi/xsv"
"os"
"sync"
"time"
"xsv"
)

type NotUsed struct {
Expand Down Expand Up @@ -120,4 +120,21 @@ func main() {
if err != nil {
panic(err)
}

// Read to struct slice
clientsFile2, err := os.Open("clients.csv")
if err != nil {
panic(err)
}

var readClients []*Client
xsvRead := xsv.NewXsvRead[*Client]()
xsvRead.From = 2
xsvRead.To = 4
if err := xsvRead.SetFileReader(clientsFile2).ReadTo(&readClients); err != nil {
panic(err)
}
for _, readClient := range readClients {
fmt.Println(*readClient)
}
}
47 changes: 42 additions & 5 deletions xsv_read.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@ package xsv
import (
"bytes"
"encoding/csv"
"errors"
"fmt"
"os"
"strconv"
"strings"
)

// XsvRead manages configuration values related to the csv read process.
type XsvRead[T any] struct {
TagName string //key in the struct field's tag to scan
TagSeparator string //separator string for multiple csv tags in struct fields
FailIfUnmatchedStructTags bool // indicates whether it is considered an error when there is an unmatched struct tag.
FailIfDoubleHeaderNames bool // indicates whether it is considered an error when a header name is repeated in the csv header.
ShouldAlignDuplicateHeadersWithStructFieldOrder bool // indicates whether we should align duplicate CSV headers per their alignment in the struct definition.
TagName string //key in the struct field's tag to scan
TagSeparator string //separator string for multiple csv tags in struct fields
FailIfUnmatchedStructTags bool // indicates whether it is considered an error when there is an unmatched struct tag.
FailIfDoubleHeaderNames bool // indicates whether it is considered an error when a header name is repeated in the csv header.
ShouldAlignDuplicateHeadersWithStructFieldOrder bool // indicates whether we should align duplicate CSV headers per their alignment in the struct definition.
From int //
To int //
OnRecord func(T) T // callback function to be called on each record
NameNormalizer Normalizer
ErrorHandler ErrorHandler
Expand All @@ -27,12 +32,44 @@ func NewXsvRead[T any]() *XsvRead[T] {
FailIfUnmatchedStructTags: false,
FailIfDoubleHeaderNames: false,
ShouldAlignDuplicateHeadersWithStructFieldOrder: false,
From: 1,
To: -1,
OnRecord: nil,
NameNormalizer: func(s string) string { return s },
ErrorHandler: nil,
}
}

func (x *XsvRead[T]) checkFrom() (err error) {
if x.From >= 0 {
return nil
}
return errors.New(fmt.Sprintf("%s cannot be set to a negative value.", strconv.Quote("From")))
}

func (x *XsvRead[T]) checkTo() (err error) {
if x.To >= -1 {
return nil
}
return errors.New(fmt.Sprintf("%s cannot be set to a negative value other than -1.", strconv.Quote("To")))
}

func (x *XsvRead[T]) checkFromTo() (err error) {
if err := x.checkFrom(); err != nil {
return err
}
if err := x.checkTo(); err != nil {
return err
}
if x.To == -1 {
return nil
}
if x.From <= x.To {
return nil
}
return errors.New(fmt.Sprintf("%s cannot be set before %s", strconv.Quote("To"), strconv.Quote("From")))
}

func (x *XsvRead[T]) SetReader(r *csv.Reader) (xr *XsvReader[T]) {
xr = NewXsvReader(*x)
xr.reader = r
Expand Down
54 changes: 54 additions & 0 deletions xsv_read_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package xsv

import (
"testing"
)

func TestXsvRead_checkFromTo(t *testing.T) {
type Arg struct {
From int
To int
}
type testCase[T any] struct {
name string
arg Arg
wantErr bool
}
tests := []testCase[Arg]{
{
name: "default",
arg: Arg{From: 1, To: -1},
wantErr: false,
},
{
name: "only 1 line",
arg: Arg{From: 1, To: 1},
wantErr: false,
},
{
name: "great and small reversals",
arg: Arg{From: 3, To: 2},
wantErr: true,
},
{
name: "unsigned int on From",
arg: Arg{From: -1, To: 2},
wantErr: true,
},
{
name: "unsigned int on To",
arg: Arg{From: 1, To: -2},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
xsvRead := NewXsvRead[any]()
xsvRead.From = tt.arg.From
xsvRead.To = tt.arg.To
if err := xsvRead.checkFromTo(); (err != nil) != tt.wantErr {
t.Errorf("checkFromTo() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
Loading
Loading