Skip to content

Commit

Permalink
Merge pull request #6 from go-andiamo/options
Browse files Browse the repository at this point in the history
Options
  • Loading branch information
marrow16 committed Oct 29, 2022
2 parents ae4f015 + 230099b commit 1c47c4f
Show file tree
Hide file tree
Showing 8 changed files with 1,144 additions and 332 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func main() {
println(len(parts))
}
```
would yield 5 - instead of the desired 3
would yield 5 ([try on go-playground](https://go.dev/play/p/bEnwjc-gfQS)) - instead of the desired 3

However, with splitter, the result would be different...
```go
Expand All @@ -41,7 +41,7 @@ func main() {
println(len(parts))
}
```
which yields the desired 3!
which yields the desired 3! [try on go-playground](https://go.dev/play/p/lIae-RjzSe6)

Note: The varargs, after the first separator arg, are the desired 'enclosures' (e.g. quotes, brackets, etc.) to be taken
into consideration
Expand All @@ -62,8 +62,9 @@ func main() {
parts, _ := commaSplitter.Split(str)
println(len(parts))
}

```
[try on go-playground](https://go.dev/play/p/wgJ68hXBp1n)

Or with double escaping...
```go
package main
Expand All @@ -78,6 +79,7 @@ func main() {
println(len(parts))
}
```
[try on go-playground](https://go.dev/play/p/3BpayDZyaA7)

#### Not separating when separator encountered in quotes or brackets...
```go
Expand All @@ -90,7 +92,7 @@ import (

func main() {
encs := []*splitter.Enclosure{
splitter.Brackets, splitter.SquareBrackets, splitter.CurlyBrackets,
splitter.Parenthesis, splitter.SquareBrackets, splitter.CurlyBrackets,
splitter.DoubleQuotesDoubleEscaped, splitter.SingleQuotesDoubleEscaped,
}
commaSplitter, _ := splitter.NewSplitter(',', encs...)
Expand All @@ -103,3 +105,4 @@ func main() {
}
}
```
[try on go-playground](https://go.dev/play/p/bvzC1NXfG3z)
101 changes: 85 additions & 16 deletions errors.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,44 @@
package splitter

import "fmt"
import (
"fmt"
)

// SplittingErrorType is the splitting error type - as used by splittingError
type SplittingErrorType int

const (
Unopened SplittingErrorType = iota
Unclosed
OptionFail
Wrapped
)

type SplittingError struct {
Type SplittingErrorType
Position int
Rune rune
Enclosure *Enclosure
// SplittingError is the error type always returned from Splitter.Split
type SplittingError interface {
error
Type() SplittingErrorType
Position() int
Rune() rune
Enclosure() *Enclosure
Wrapped() error
}

type splittingError struct {
errorType SplittingErrorType
position int
rune rune
enc *Enclosure
wrapped error
message string
}

func newSplittingError(t SplittingErrorType, pos int, r rune, enc *Enclosure) error {
return &SplittingError{
Type: t,
Position: pos,
Rune: r,
Enclosure: enc,
func newSplittingError(t SplittingErrorType, pos int, r rune, enc *Enclosure) SplittingError {
return &splittingError{
errorType: t,
position: pos,
rune: r,
enc: enc,
}
}

Expand All @@ -30,9 +47,61 @@ const (
unclosedFmt = "unclosed '%s' at position %d"
)

func (e *SplittingError) Error() string {
if e.Type == Unopened {
return fmt.Sprintf(unopenedFmt, string(e.Rune), e.Position)
func (e *splittingError) Error() string {
if e.errorType == Unopened {
return fmt.Sprintf(unopenedFmt, string(e.rune), e.position)
} else if e.errorType == Unclosed {
return fmt.Sprintf(unclosedFmt, string(e.rune), e.position)
} else if e.wrapped != nil {
return e.wrapped.Error()
}
return e.message
}

func (e *splittingError) Type() SplittingErrorType {
return e.errorType
}
func (e *splittingError) Position() int {
return e.position
}
func (e *splittingError) Rune() rune {
return e.rune
}
func (e *splittingError) Enclosure() *Enclosure {
return e.enc
}
func (e *splittingError) Wrapped() error {
return e.wrapped
}

func asSplittingError(err error, pos int) SplittingError {
if err != nil {
if se, ok := err.(SplittingError); ok {
return se
} else {
return &splittingError{
errorType: Wrapped,
position: pos,
wrapped: err,
}
}
}
return nil
}

func NewOptionFailError(msg string, pos int, subPart SubPart) SplittingError {
if subPart != nil {
return &splittingError{
errorType: OptionFail,
position: subPart.StartPos(),
rune: subPart.StartRune(),
enc: subPart.Enclosure(),
message: msg,
}
}
return &splittingError{
errorType: OptionFail,
position: pos,
message: msg,
}
return fmt.Sprintf(unclosedFmt, string(e.Rune), e.Position)
}
127 changes: 127 additions & 0 deletions errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package splitter

import (
"errors"
"fmt"
"github.com/stretchr/testify/require"
"testing"
)

func TestWrappedSplittingError(t *testing.T) {
sErr := &splittingError{
errorType: Wrapped,
wrapped: errors.New("whoops"),
}
require.Error(t, sErr)
require.Equal(t, "whoops", sErr.Error())
}

func TestWrappedAsSplittingError(t *testing.T) {
sErr := asSplittingError(errors.New("whoops"), 16)
require.Error(t, sErr)
require.Equal(t, "whoops", sErr.Error())

sErr2 := asSplittingError(sErr, 0)
require.Error(t, sErr2)
require.Equal(t, sErr, sErr2)

var err error
sErr = asSplittingError(err, 0)
require.NoError(t, sErr)
require.Nil(t, sErr)

}

func TestNewSplittingError(t *testing.T) {
err := newSplittingError(Unopened, 16, '(', Parenthesis)
require.Error(t, err)
require.Equal(t, fmt.Sprintf(unopenedFmt, "(", 16), err.Error())

err = newSplittingError(Unclosed, 16, ')', Parenthesis)
require.Equal(t, fmt.Sprintf(unclosedFmt, ")", 16), err.Error())
}

func TestSplittingError_DefaultMessage(t *testing.T) {
err := &splittingError{
errorType: -1,
message: "whoops",
}
require.Error(t, err)
require.Equal(t, "whoops", err.Error())
}

func TestNewOptionFailError(t *testing.T) {
err := NewOptionFailError("whoops", 16, nil)
require.Error(t, err)
require.Equal(t, "whoops", err.Error())
sErr, ok := err.(*splittingError)
require.True(t, ok)
require.Equal(t, OptionFail, sErr.Type())
require.Equal(t, 16, sErr.Position())

subPart := &subPart{
enc: *Parenthesis,
openPos: 5,
closePos: 10,
}
err = NewOptionFailError("whoops", 0, subPart)
require.Error(t, err)
require.Equal(t, "whoops", err.Error())
sErr, ok = err.(*splittingError)
require.True(t, ok)
require.Equal(t, OptionFail, sErr.Type())
require.Equal(t, 5, sErr.Position())
}

func TestSplitAlwaysReturnsSplittingError(t *testing.T) {
s, err := NewSplitter(',', Parenthesis)
require.NoError(t, err)
s.AddDefaultOptions(NotEmptyFirst)

_, err = s.Split(`(`)
require.Error(t, err)
sErr, ok := err.(SplittingError)
require.True(t, ok)
require.Equal(t, Unclosed, sErr.Type())
require.Equal(t, '(', sErr.Rune())
require.Equal(t, 0, sErr.Position())
require.Equal(t, Parenthesis, sErr.Enclosure())
require.Nil(t, sErr.Wrapped())

_, err = s.Split(`)`)
require.Error(t, err)
sErr, ok = err.(SplittingError)
require.True(t, ok)
require.Equal(t, Unopened, sErr.Type())
require.Equal(t, ')', sErr.Rune())
require.Equal(t, 0, sErr.Position())
require.Equal(t, Parenthesis, sErr.Enclosure())
require.Nil(t, sErr.Wrapped())

_, err = s.Split(`,a`)
require.Error(t, err)
sErr, ok = err.(SplittingError)
require.True(t, ok)
require.Equal(t, OptionFail, sErr.Type())
require.Equal(t, rune(0), sErr.Rune())
require.Equal(t, 0, sErr.Position())
require.Nil(t, sErr.Enclosure())
require.Nil(t, sErr.Wrapped())

_, err = s.Split(`a`, &errorOption{})
require.Error(t, err)
sErr, ok = err.(SplittingError)
require.True(t, ok)
require.Equal(t, Wrapped, sErr.Type())
require.Error(t, sErr.Wrapped())
require.Equal(t, rune(0), sErr.Rune())
require.Nil(t, sErr.Enclosure())
require.Equal(t, 0, sErr.Position())
}

type errorOption struct {
}

func (o *errorOption) Apply(s string, pos int, totalLen int, captured int, skipped int, isLast bool, subParts ...SubPart) (string, bool, error) {
return s, false, errors.New("error option")
}
Loading

0 comments on commit 1c47c4f

Please sign in to comment.