Skip to content

Commit

Permalink
Merge pull request #10 from go-andiamo/add-options
Browse files Browse the repository at this point in the history
Add more options
  • Loading branch information
marrow16 committed Oct 30, 2022
2 parents fd090e4 + ead8781 commit 084b179
Show file tree
Hide file tree
Showing 5 changed files with 328 additions and 6 deletions.
100 changes: 100 additions & 0 deletions encosures_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package splitter

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

var testEnclosures = map[string]*Enclosure{
"DoubleQuotes": DoubleQuotes,
"DoubleQuotesBackSlashEscaped": DoubleQuotesBackSlashEscaped,
"DoubleQuotesDoubleEscaped": DoubleQuotesDoubleEscaped,
"SingleQuotes": SingleQuotes,
"SingleQuotesBackSlashEscaped": SingleQuotesBackSlashEscaped,
"SingleQuotesDoubleEscaped": SingleQuotesDoubleEscaped,
"SingleInvertedQuotes": SingleInvertedQuotes,
"SingleInvertedQuotesBackSlashEscaped": SingleInvertedQuotesBackSlashEscaped,
"SingleInvertedQuotesDoubleEscaped": SingleInvertedQuotesDoubleEscaped,
"DoublePointingAngleQuotes": DoublePointingAngleQuotes,
"SinglePointingAngleQuotes": SinglePointingAngleQuotes,
"SinglePointingAngleQuotesBackSlashEscaped": SinglePointingAngleQuotesBackSlashEscaped,
"LeftRightDoubleDoubleQuotes": LeftRightDoubleDoubleQuotes,
"LeftRightDoubleSingleQuotes": LeftRightDoubleSingleQuotes,
"LeftRightDoublePrimeQuotes": LeftRightDoublePrimeQuotes,
"SingleLowHigh9Quotes": SingleLowHigh9Quotes,
"DoubleLowHigh9Quotes": DoubleLowHigh9Quotes,
"Parenthesis": Parenthesis,
"CurlyBrackets": CurlyBrackets,
"SquareBrackets": SquareBrackets,
"LtGtAngleBrackets": LtGtAngleBrackets,
"LeftRightPointingAngleBrackets": LeftRightPointingAngleBrackets,
"SubscriptParenthesis": SubscriptParenthesis,
"SuperscriptParenthesis": SuperscriptParenthesis,
"SmallParenthesis": SmallParenthesis,
"SmallCurlyBrackets": SmallCurlyBrackets,
"DoubleParenthesis": DoubleParenthesis,
"MathWhiteSquareBrackets": MathWhiteSquareBrackets,
"MathAngleBrackets": MathAngleBrackets,
"MathDoubleAngleBrackets": MathDoubleAngleBrackets,
"MathWhiteTortoiseShellBrackets": MathWhiteTortoiseShellBrackets,
"MathFlattenedParenthesis": MathFlattenedParenthesis,
"OrnateParenthesis": OrnateParenthesis,
"AngleBrackets": AngleBrackets,
"DoubleAngleBrackets": DoubleAngleBrackets,
"FullWidthParenthesis": FullWidthParenthesis,
"FullWidthSquareBrackets": FullWidthSquareBrackets,
"FullWidthCurlyBrackets": FullWidthCurlyBrackets,
"SubstitutionBrackets": SubstitutionBrackets,
"SubstitutionQuotes": SubstitutionQuotes,
"DottedSubstitutionBrackets": DottedSubstitutionBrackets,
"DottedSubstitutionQuotes": DottedSubstitutionQuotes,
"TranspositionBrackets": TranspositionBrackets,
"TranspositionQuotes": TranspositionQuotes,
"RaisedOmissionBrackets": RaisedOmissionBrackets,
"RaisedOmissionQuotes": RaisedOmissionQuotes,
"LowParaphraseBrackets": LowParaphraseBrackets,
"LowParaphraseQuotes": LowParaphraseQuotes,
"SquareWithQuillBrackets": SquareWithQuillBrackets,
"WhiteParenthesis": WhiteParenthesis,
"WhiteCurlyBrackets": WhiteCurlyBrackets,
"WhiteSquareBrackets": WhiteSquareBrackets,
"WhiteLenticularBrackets": WhiteLenticularBrackets,
"WhiteTortoiseShellBrackets": WhiteTortoiseShellBrackets,
"FullWidthWhiteParenthesis": FullWidthWhiteParenthesis,
"BlackTortoiseShellBrackets": BlackTortoiseShellBrackets,
"BlackLenticularBrackets": BlackLenticularBrackets,
"PointingCurvedAngleBrackets": PointingCurvedAngleBrackets,
"TortoiseShellBrackets": TortoiseShellBrackets,
"SmallTortoiseShellBrackets": SmallTortoiseShellBrackets,
"ZNotationImageBrackets": ZNotationImageBrackets,
"ZNotationBindingBrackets": ZNotationBindingBrackets,
"MediumOrnamentalParenthesis": MediumOrnamentalParenthesis,
"LightOrnamentalTortoiseShellBrackets": LightOrnamentalTortoiseShellBrackets,
"MediumOrnamentalFlattenedParenthesis": MediumOrnamentalFlattenedParenthesis,
"MediumOrnamentalPointingAngleBrackets": MediumOrnamentalPointingAngleBrackets,
"MediumOrnamentalCurlyBrackets": MediumOrnamentalCurlyBrackets,
"HeavyOrnamentalPointingAngleQuotes": HeavyOrnamentalPointingAngleQuotes,
"HeavyOrnamentalPointingAngleBrackets": HeavyOrnamentalPointingAngleBrackets,
}

func TestEnclosures(t *testing.T) {
for name, enc := range testEnclosures {
t.Run(fmt.Sprintf("%s", name), func(t *testing.T) {
require.NotEqual(t, rune(0), enc.Start)
require.NotEqual(t, rune(0), enc.End)
if strings.Contains(name, "Quote") {
require.True(t, enc.IsQuote)
require.Equal(t, enc.Escape == rune(0), !enc.Escapable)
if enc.Escapable {
require.NotEqual(t, rune(0), enc.Escape)
}
} else {
require.False(t, enc.IsQuote)
require.False(t, enc.Escapable)
require.Equal(t, rune(0), enc.Escape)
}
})
}
}
12 changes: 11 additions & 1 deletion errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package splitter

import (
"fmt"
"strings"
)

// SplittingErrorType is the splitting error type - as used by splittingError
Expand All @@ -22,6 +23,7 @@ type SplittingError interface {
Rune() rune
Enclosure() *Enclosure
Wrapped() error
Unwrap() error
}

type splittingError struct {
Expand Down Expand Up @@ -55,7 +57,15 @@ func (e *splittingError) Error() string {
} else if e.wrapped != nil {
return e.wrapped.Error()
}
return e.message
result := fmt.Sprintf(e.message, e.position)
if strings.HasSuffix(result, fmt.Sprintf(`%%!(EXTRA int=%d)`, e.position)) {
result = result[:strings.LastIndex(result, "%!(EXTRA int=")]
}
return result
}

func (e *splittingError) Unwrap() error {
return e.wrapped
}

func (e *splittingError) Type() SplittingErrorType {
Expand Down
12 changes: 11 additions & 1 deletion errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,11 @@ func TestNewOptionFailError(t *testing.T) {
func TestSplitAlwaysReturnsSplittingError(t *testing.T) {
s, err := NewSplitter(',', Parenthesis)
require.NoError(t, err)
s.AddDefaultOptions(NotEmptyFirst)
s.AddDefaultOptions(NotEmptyFirstMsg("whoops at position %d"))

_, err = s.Split(`(`)
require.Error(t, err)
require.Equal(t, fmt.Sprintf(unclosedFmt, `(`, 0), err.Error())
sErr, ok := err.(SplittingError)
require.True(t, ok)
require.Equal(t, Unclosed, sErr.Type())
Expand All @@ -90,30 +91,39 @@ func TestSplitAlwaysReturnsSplittingError(t *testing.T) {

_, err = s.Split(`)`)
require.Error(t, err)
require.Equal(t, fmt.Sprintf(unopenedFmt, `)`, 0), err.Error())
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())
require.NoError(t, sErr.Unwrap())
require.NoError(t, errors.Unwrap(sErr))

_, err = s.Split(`,a`)
require.Error(t, err)
require.Equal(t, `whoops at position 0`, err.Error())
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())
require.NoError(t, sErr.Unwrap())
require.NoError(t, errors.Unwrap(sErr))

_, err = s.Split(`a`, &errorOption{})
require.Error(t, err)
require.Equal(t, "error option", err.Error())
sErr, ok = err.(SplittingError)
require.True(t, ok)
require.Equal(t, Wrapped, sErr.Type())
require.Error(t, sErr.Wrapped())
require.Error(t, sErr.Unwrap())
require.Error(t, errors.Unwrap(sErr))
require.Equal(t, rune(0), sErr.Rune())
require.Nil(t, sErr.Enclosure())
require.Equal(t, 0, sErr.Position())
Expand Down
64 changes: 62 additions & 2 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ var (
NotEmptyLast Option = _NotEmptyLast // NotEmptyLast causes an error if the last split part is empty
NotEmptyLastMsg = _NotEmptyLastMsg // NotEmptyLastMsg is the same as NotEmptyLast but allows a custom error message
IgnoreEmptyLast Option = _IgnoreEmptyLast // IgnoreEmptyLast causes an empty last split part not to be added to the result
NotEmptyInners Option = _NotEmptyInners // NotEmptyInners causes an error if an inner (i.e. not first or last) split part is empty
NotEmptyInnersMsg = _NotEmptyInnersMsg // NotEmptyInnersMsg is the same as NotEmptyInners but allows a custom error message
IgnoreEmptyInners Option = _IgnoreEmptyInners // IgnoreEmptyInners causes empty inner (i.e. not first or last) split parts not to be added to the result
NotEmptyOuters Option = _NotEmptyOuters // NotEmptyOuters causes an error if an outer (i.e. first or last) split part is empty (same as adding both NotEmptyFirst & NotEmptyLast)
NotEmptyOutersMsg = _NotEmptyOutersMsg // NotEmptyOutersMsg is the same as NotEmptyOuters but allows a custom error message
IgnoreEmptyOuters Option = _IgnoreEmptyOuters // IgnoreEmptyOuters causes empty outer (i.e. first or last) split parts not to be added to the result (same as adding both IgnoreEmptyFirst & IgnoreEmptyLast)
NoContiguousQuotes Option = _NoContiguousQuotes // NoContiguousQuotes causes an error if there are contiguous quotes within a split part
NoContiguousQuotesMsg = _NoContiguousQuotesMsg // NoContiguousQuotesMsg is the same as NoContiguousQuotes but allows a custom error message
NoMultiQuotes Option = _NoMultiQuotes // NoMultiQuotes causes an error if there are multiple (not necessarily contiguous) quotes within a split part
Expand Down Expand Up @@ -49,7 +55,17 @@ var (
_NotEmptyLastMsg = func(message string) Option {
return &notEmptyLast{message: message}
}
_IgnoreEmptyLast = &ignoreEmptyLast{}
_IgnoreEmptyLast = &ignoreEmptyLast{}
_NotEmptyInners = &notEmptyInners{message: "inner items cannot be empty"}
_NotEmptyInnersMsg = func(message string) Option {
return &notEmptyInners{message: message}
}
_IgnoreEmptyInners = &ignoreEmptyInners{}
_NotEmptyOuters = &notEmptyOuters{message: "first/last items cannot be empty"}
_NotEmptyOutersMsg = func(message string) Option {
return &notEmptyOuters{message: message}
}
_IgnoreEmptyOuters = &ignoreEmptyOuters{}
_NoContiguousQuotes = &noContiguousQuotes{message: "split item cannot have contiguous quotes"}
_NoContiguousQuotesMsg = func(message string) Option {
return &noContiguousQuotes{message: message}
Expand Down Expand Up @@ -121,7 +137,7 @@ type notEmptyLast struct {
}

func (o *notEmptyLast) Apply(s string, pos int, totalLen int, captured int, skipped int, isLast bool, subParts ...SubPart) (string, bool, error) {
if s == "" && pos == totalLen {
if s == "" && isLast {
return "", false, NewOptionFailError(o.message, pos, nil)
}
return s, true, nil
Expand All @@ -137,6 +153,50 @@ func (o *ignoreEmptyLast) Apply(s string, pos int, totalLen int, captured int, s
return s, true, nil
}

type notEmptyInners struct {
message string
}

func (o *notEmptyInners) Apply(s string, pos int, totalLen int, captured int, skipped int, isLast bool, subParts ...SubPart) (string, bool, error) {
if s == "" && !isLast && !(captured == 0 && skipped == 0) {
return "", false, NewOptionFailError(o.message, pos, nil)
}
return s, true, nil
}

type ignoreEmptyInners struct {
}

func (o *ignoreEmptyInners) Apply(s string, pos int, totalLen int, captured int, skipped int, isLast bool, subParts ...SubPart) (string, bool, error) {
if s != "" {
return s, true, nil
}
isInner := !isLast && !(captured == 0 && skipped == 0)
return s, !isInner, nil
}

type notEmptyOuters struct {
message string
}

func (o *notEmptyOuters) Apply(s string, pos int, totalLen int, captured int, skipped int, isLast bool, subParts ...SubPart) (string, bool, error) {
if s == "" && (isLast || (captured == 0 && skipped == 0)) {
return "", false, NewOptionFailError(o.message, pos, nil)
}
return s, true, nil
}

type ignoreEmptyOuters struct {
}

func (o *ignoreEmptyOuters) Apply(s string, pos int, totalLen int, captured int, skipped int, isLast bool, subParts ...SubPart) (string, bool, error) {
if s != "" {
return s, true, nil
}
isOuter := isLast || (captured == 0 && skipped == 0)
return s, !isOuter, nil
}

type noContiguousQuotes struct {
message string
}
Expand Down
Loading

0 comments on commit 084b179

Please sign in to comment.