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

Use scanner in reader #4

Merged
merged 4 commits into from
May 28, 2022
Merged
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
41 changes: 32 additions & 9 deletions reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ import (
"strings"
)

// TODO(chore): add comments

// Reader ...
// Reader describes a tuples reader.
type Reader struct {
s *scanner
}

// NewReader ...
// NewReader creates a new instance of the Reader.
// If reader creation fails it returns error.
func NewReader(r io.Reader, opts ...ReaderOption) (*Reader, error) {
ropts := defaultReaderOptions
for _, opt := range opts {
Expand All @@ -28,12 +27,15 @@ func NewReader(r io.Reader, opts ...ReaderOption) (*Reader, error) {
return &Reader{s}, nil
}

// Read ...
func (r *Reader) Read() (tuple []string, err error) {
// Read reads one tuple at a time and returns fields values in the order
// they appear in the string. It returns error when read fails or when
// reached the end of the tuples input.
func (r *Reader) Read() ([]string, error) {
return r.readTuple()
}

// ReadAll ...
// ReadAll reads all tuples from the input. It returns a slice of tuples values.
// It returns error when reader initialisation failed or read process failed.
func (r *Reader) ReadAll() (tuples [][]string, err error) {
for {
tuple, err := r.readTuple()
Expand Down Expand Up @@ -67,7 +69,19 @@ func (r *Reader) readTuple() ([]string, error) {
return nil, err
}

// ReadString ...
// ReadString reads all tuples from the string. It returns a slice of tuples
// values. It returns error when reader initialisation failed or read process
// failed.
//
// Usage:
// tuples, err := ReadString("fname=John,lname=Doe dob=2000-01-01 age=17")
// if err != nil {
// return err
// }
// fmt.Println(tuples)
//
// // Output:
// // [[John Doe] [2000-01-01] [17]]
func ReadString(s string, opts ...ReaderOption) ([][]string, error) {
r, err := NewReader(strings.NewReader(s), opts...)
if err != nil {
Expand All @@ -81,14 +95,23 @@ type readerOptions struct {
keyValDelimiter rune
}

// ReaderOption describes a reader option, i.e fields delimiter, key-value
// delimiter, etc.
type ReaderOption func(*readerOptions)

// WithFieldsDelimiter sets a custom fields delimiter option for reader.
// Default delimiter is ','.
func WithFieldsDelimiter(d rune) ReaderOption {
return func(ro *readerOptions) { ro.fieldsDelimiter = d }
}

// WithFieldsDelimiter sets a custom key-value delimiter option for reader.
// Default delimiter is '='.
func WithKeyValueDelimiter(d rune) ReaderOption {
return func(ro *readerOptions) { ro.keyValDelimiter = d }
}

var defaultReaderOptions = readerOptions{fieldsDelimiter: ',', keyValDelimiter: '='}
var defaultReaderOptions = readerOptions{
fieldsDelimiter: ',',
keyValDelimiter: '=',
}
54 changes: 45 additions & 9 deletions reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ var readTests = []readTest{{
out: [][]string{{"John", "Doe", "2000-01-01"}, {"Bob", "Smith", "2010-10-10"}},
fDelim: ';',
kvDelim: ':',
}, {
desc: "Fails to read tuple",
in: "fname,lname=Doe",
err: errors.New("tuples: scan failed: tuple #1 invalid field #1"),
}}

func newReader(rt readTest) (*tuples.Reader, error) {
Expand Down Expand Up @@ -133,6 +137,13 @@ func TestRead(t *testing.T) {
if err != nil && err.Error() != wantErr.Error() {
t.Fatalf("#%d: Read() error at record %d:\ngot %v\nwant %v", tI, recNum, err, wantErr)
}
if err != nil && err != io.EOF {
// all non EOF errors expected to be a ScannerError
var e *tuples.ScannerError
if !errors.As(err, &e) {
t.Errorf("#%d: Read() error is not a ScannerError", tI)
}
}
if err != nil {
break
}
Expand Down Expand Up @@ -172,18 +183,17 @@ func TestReadAll(t *testing.T) {
}
}

// TODO (chore): read string error test
// TODO (chore): read error test
// TODO (chore): readAll read error test

func TestReadString(t *testing.T) {
for tI, tC := range readTests {
if tC.fDelim != 0 || tC.kvDelim != 0 {
// read string creates a reader with default delimiters
// tests with custom delimters fail
continue
var opts []tuples.ReaderOption
if tC.fDelim != 0 {
opts = append(opts, tuples.WithFieldsDelimiter(tC.fDelim))
}
if tC.kvDelim != 0 {
opts = append(opts, tuples.WithKeyValueDelimiter(tC.kvDelim))
}
out, err := tuples.ReadString(tC.in)

out, err := tuples.ReadString(tC.in, opts...)
if tC.err != nil {
if err == nil || (err.Error() != tC.err.Error()) {
t.Fatalf("#%d: ReadString() error mismatch:\ngot %v\nwant %v", tI, err, tC.err)
Expand All @@ -201,3 +211,29 @@ func TestReadString(t *testing.T) {
}
}
}

func TestReadStringFails(t *testing.T) {
for tI, tC := range newReaderTests {
t.Run(tC.desc, func(t *testing.T) {
var opts []tuples.ReaderOption
if tC.fDelim != 0 {
opts = append(opts, tuples.WithFieldsDelimiter(tC.fDelim))
}
if tC.kvDelim != 0 {
opts = append(opts, tuples.WithKeyValueDelimiter(tC.kvDelim))
}

out, err := tuples.ReadString("", opts...)
if err == nil || (err.Error() != tC.err.Error()) {
t.Fatalf("#%d: ReadString error mismatch:\ngot %v,\nwant %v", tI, err, tC.err)
}
var e *tuples.InvalidScannerOptionError
if !errors.As(err, &e) {
t.Errorf("#%d: ReadString() error is not a InvalidScannerOptionError", tI)
}
if out != nil {
t.Errorf("#%d: ReadString() output:\ngot %v\nwant nil", tI, out)
}
})
}
}
3 changes: 1 addition & 2 deletions scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,7 @@ func newScanner(r io.Reader, opts ...scannerOption) (*scanner, error) {
// more to make sure that there is nothing left. next does not scan tuple
// proactively.
//
// Example of scanner use:
//
// Usage:
// in := strings.NewReader("name=Rob,lname=Doe name=Bob,lname=Smith")
// s := newScanner(in)
// for s.next() {
Expand Down