Skip to content

Commit

Permalink
Use sync.Pool in the StdConverter, StdTokenizer, adds text tests, upd…
Browse files Browse the repository at this point in the history
…ates build action

* Adds usage of sync.Pool in StdConverter, StdTokenizer

* Makes tests run in parallel

* Adds text.Test tests

* Updates github actions
  • Loading branch information
chanced authored Sep 29, 2022
1 parent d82e985 commit 7650644
Show file tree
Hide file tree
Showing 8 changed files with 1,495 additions and 23 deletions.
31 changes: 30 additions & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,41 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: ">=1.19"
check-latest: true
cache: true
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...
build-1-18:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18
check-latest: true
cache: true
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...
build-1-19:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
check-latest: true
cache: true
- name: Build
run: go build -v ./...

Expand Down
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
[![Coverage](http://gocover.io/_badge/github.com/chanced/caps)](http://gocover.io/github.com/chanced/caps)
![Build Status](https://img.shields.io/github/workflow/status/chanced/caps/Build?style=flat-square)


caps is a unicode aware, case conversion library for Go. It
was built with the following priorites in mind: configurability, consistency,
correctness, ergonomic, and reasonable performance; in that order.
Expand Down Expand Up @@ -319,9 +318,7 @@ func main() {
The `text` package contains two types:

- `Text` which has all of the case conversions and relevant functions from strings as methods.
- `Texts` which is a slice of Text

It does not currently have tests.
- `Texts` which is a sortable slice of Text with a few helper methods

```go
package main
Expand Down
24 changes: 24 additions & 0 deletions caps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ var titleTestCases = testcases{
func TestToTitle(t *testing.T) {
for _, test := range titleTestCases {
t.Run(test.input, func(t *testing.T) {
t.Parallel()
output := caps.ToTitle(test.input, test.opts...)
if output != test.expected {
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
Expand All @@ -76,12 +77,14 @@ func TestToTitle(t *testing.T) {

for _, test := range titleTestCases {
t.Run(test.input, func(t *testing.T) {
t.Parallel()
output := caps.ToTitle(test.input, test.opts...)
if output != test.expected {
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
}
})
t.Run("Caps::"+test.input, func(t *testing.T) {
t.Parallel()
c := caps.New(test.opts.toCapsOpts())
output := c.ToTitle(test.input)
if output != test.expected {
Expand Down Expand Up @@ -110,12 +113,14 @@ var camelTestCases = testcases{
func TestToCamel(t *testing.T) {
for _, test := range camelTestCases {
t.Run(test.input, func(t *testing.T) {
t.Parallel()
output := caps.ToCamel(test.input, test.opts...)
if output != test.expected {
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
}
})
t.Run("Caps::"+test.input, func(t *testing.T) {
t.Parallel()
c := caps.New(test.opts.toCapsOpts())
output := c.ToCamel(test.input)
if output != test.expected {
Expand Down Expand Up @@ -145,12 +150,14 @@ var lowerCamelTestCases = testcases{
func TestToLowerCamel(t *testing.T) {
for _, test := range lowerCamelTestCases {
t.Run(test.input, func(t *testing.T) {
t.Parallel()
output := caps.ToLowerCamel(test.input, test.opts...)
if output != test.expected {
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
}
})
t.Run("Caps::"+test.input, func(t *testing.T) {
t.Parallel()
c := caps.New(test.opts.toCapsOpts())
output := c.ToLowerCamel(test.input)
if output != test.expected {
Expand Down Expand Up @@ -179,12 +186,14 @@ var kebabTestCases = testcases{
func TestToKebab(t *testing.T) {
for _, test := range kebabTestCases {
t.Run(test.input, func(t *testing.T) {
t.Parallel()
output := caps.ToKebab(test.input, test.opts...)
if output != test.expected {
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
}
})
t.Run("Caps::"+test.input, func(t *testing.T) {
t.Parallel()
c := caps.New(test.opts.toCapsOpts())
output := c.ToKebab(test.input)
if output != test.expected {
Expand Down Expand Up @@ -213,12 +222,14 @@ var screamingKebabTestCases = testcases{
func TestToScreamingKebab(t *testing.T) {
for _, test := range screamingKebabTestCases {
t.Run(test.input, func(t *testing.T) {
t.Parallel()
output := caps.ToScreamingKebab(test.input, test.opts...)
if output != test.expected {
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
}
})
t.Run("Caps::"+test.input, func(t *testing.T) {
t.Parallel()
c := caps.New(test.opts.toCapsOpts())
output := c.ToScreamingKebab(test.input)
if output != test.expected {
Expand Down Expand Up @@ -247,12 +258,14 @@ var snakeTestCases = testcases{
func TestToSnake(t *testing.T) {
for _, test := range snakeTestCases {
t.Run(test.input, func(t *testing.T) {
t.Parallel()
output := caps.ToSnake(test.input, test.opts...)
if output != test.expected {
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
}
})
t.Run("Caps::"+test.input, func(t *testing.T) {
t.Parallel()
c := caps.New(test.opts.toCapsOpts())
output := c.ToSnake(test.input)
if output != test.expected {
Expand Down Expand Up @@ -281,12 +294,14 @@ var screamingSnakeTestCases = testcases{
func TestToScreamingSnake(t *testing.T) {
for _, test := range screamingSnakeTestCases {
t.Run(test.input, func(t *testing.T) {
t.Parallel()
output := caps.ToScreamingSnake(test.input, test.opts...)
if output != test.expected {
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
}
})
t.Run("Caps::"+test.input, func(t *testing.T) {
t.Parallel()
c := caps.New(test.opts.toCapsOpts())
output := c.ToScreamingSnake(test.input)
if output != test.expected {
Expand Down Expand Up @@ -315,12 +330,14 @@ var dotNotationTestCases = testcases{
func TestToDotNotation(t *testing.T) {
for _, test := range dotNotationTestCases {
t.Run(test.input, func(t *testing.T) {
t.Parallel()
output := caps.ToDotNotation(test.input, test.opts...)
if output != test.expected {
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
}
})
t.Run("Caps::"+test.input, func(t *testing.T) {
t.Parallel()
c := caps.New(test.opts.toCapsOpts())
output := c.ToDotNotation(test.input)
if output != test.expected {
Expand Down Expand Up @@ -349,12 +366,14 @@ var screamingDotNotationTestCases = testcases{
func TestToDelimited(t *testing.T) {
for _, test := range dotNotationTestCases {
t.Run(test.input, func(t *testing.T) {
t.Parallel()
output := caps.ToDelimited(test.input, ".", true, test.opts...)
if output != test.expected {
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
}
})
t.Run("Caps::"+test.input, func(t *testing.T) {
t.Parallel()
c := caps.New(test.opts.toCapsOpts())
output := c.ToDelimited(test.input, ".", true)
if output != test.expected {
Expand All @@ -367,12 +386,14 @@ func TestToDelimited(t *testing.T) {
func TestToScreamingDotNotation(t *testing.T) {
for _, test := range screamingDotNotationTestCases {
t.Run(test.input, func(t *testing.T) {
t.Parallel()
output := caps.ToScreamingDotNotation(test.input, test.opts...)
if output != test.expected {
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
}
})
t.Run("Caps::"+test.input, func(t *testing.T) {
t.Parallel()
c := caps.New(test.opts.toCapsOpts())
output := c.ToScreamingDotNotation(test.input)
if output != test.expected {
Expand All @@ -384,6 +405,7 @@ func TestToScreamingDotNotation(t *testing.T) {

func TestCapsAccessors(t *testing.T) {
t.Run("ReplaceStyle", func(t *testing.T) {
t.Parallel()
c := caps.New()
rs := c.ReplaceStyle()
if rs != caps.ReplaceStyleScreaming {
Expand All @@ -400,6 +422,7 @@ func TestCapsAccessors(t *testing.T) {
}
})
t.Run("NumberRules", func(t *testing.T) {
t.Parallel()
c := caps.New()
nr := c.NumberRules()
if nr != nil {
Expand All @@ -414,6 +437,7 @@ func TestCapsAccessors(t *testing.T) {
}
})
t.Run("AllowedSymbols", func(t *testing.T) {
t.Parallel()
c := caps.New()
as := c.AllowedSymbols()
if as != "" {
Expand Down
39 changes: 24 additions & 15 deletions converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,21 @@ package caps

import (
"strings"
"sync"
"unicode"

"github.com/chanced/caps/index"
"github.com/chanced/caps/token"
)

// DefaultConverter is the default Converter instance.
var DefaultConverter = NewConverter(DefaultReplacements, DefaultTokenizer, token.DefaultCaser)
var DefaultConverter Converter = NewConverter(DefaultReplacements, DefaultTokenizer, token.DefaultCaser)

var builderPool = sync.Pool{
New: func() any {
return new(strings.Builder)
},
}

// Converter is an interface satisfied by types which can convert the case of a
// string.
Expand Down Expand Up @@ -121,8 +128,6 @@ func (sc StdConverter) Replacements() []Replacement {
Screaming: v.Screaming,
}
}
b := strings.Builder{}
b.WriteByte('.')
return res
}

Expand Down Expand Up @@ -214,10 +219,12 @@ func (sc StdConverter) writeReplaceSplit(b *strings.Builder, style Style, join s
// Convert formats the string with the desired style.
func (sc StdConverter) Convert(req ConvertRequest) string {
tokens := sc.tokenizer.Tokenize(req.Input, req.AllowedSymbols, req.NumberRules)
b := strings.Builder{}
if len(tokens) == 0 {
return ""
}
b := builderPool.Get().(*strings.Builder)
b.Reset()
defer builderPool.Put(b)
if len(req.Join) > 0 {
b.Grow(len(req.Input) + len(req.Join)*(len(tokens)-1))
} else {
Expand All @@ -235,54 +242,54 @@ func (sc StdConverter) Convert(req ConvertRequest) string {
if idx.LastMatch().HasValue() {
// appending the last match
// formatIndexedReplacement(req.Style, req.ReplaceStyle, b.Len(), idx.LastMatch()), req.Join
sc.writeIndexReplacement(&b, req.Style, req.ReplaceStyle, req.Join, idx.LastMatch())
sc.writeIndexReplacement(b, req.Style, req.ReplaceStyle, req.Join, idx.LastMatch())
}
if idx.HasPartialMatches() {
// checking to make sure it isn't a number
if token.IsNumber(token.Append(sc.caser, tok, idx.PartialMatches()), req.NumberRules) {
b.WriteString(FormatToken(sc.caser, req.Style, b.Len(), token.Append(sc.caser, tok, idx.PartialMatches())))
addedAsNumber = true
} else {
sc.writeReplaceSplit(&b, req.Style, req.Join, idx.PartialMatches())
sc.writeReplaceSplit(b, req.Style, req.Join, idx.PartialMatches())
addedAsNumber = false
}
}
if !addedAsNumber {
sc.writeToken(&b, req.Style, req.Join, tok)
sc.writeToken(b, req.Style, req.Join, tok)
}
// resetting the index
idx = sc.Index()
}
default:
if idx.HasMatched() {
sc.writeIndexReplacement(&b, req.Style, req.ReplaceStyle, req.Join, idx.LastMatch())
sc.writeIndexReplacement(b, req.Style, req.ReplaceStyle, req.Join, idx.LastMatch())
}
if idx.HasPartialMatches() {
sc.writeReplaceSplit(&b, req.Style, req.Join, idx.PartialMatches())
sc.writeReplaceSplit(b, req.Style, req.Join, idx.PartialMatches())
}
if idx.HasMatched() || idx.HasPartialMatches() {
// resetting index
idx = sc.Index()
}
if rep, ok := idx.Get(tok); ok {
sc.writeIndexReplacement(&b, req.Style, req.ReplaceStyle, req.Join, rep)
sc.writeIndexReplacement(b, req.Style, req.ReplaceStyle, req.Join, rep)
} else if isNextTokenNumber(tokens, i) {
if idx, ok = idx.Match(tok); !ok {
sc.writeToken(&b, req.Style, req.Join, tok)
sc.writeToken(b, req.Style, req.Join, tok)
idx = sc.Index()
}
} else {
sc.writeToken(&b, req.Style, req.Join, tok)
sc.writeToken(b, req.Style, req.Join, tok)
}
}
}
if idx.HasMatched() {
sc.writeIndexReplacement(&b, req.Style, req.ReplaceStyle, req.Join, idx.LastMatch())
sc.writeIndexReplacement(b, req.Style, req.ReplaceStyle, req.Join, idx.LastMatch())
// parts = append(parts, formatIndexedReplacement(req.Style, req.ReplaceStyle, len(parts), idx.LastMatch()))
}

if idx.HasPartialMatches() {
sc.writeReplaceSplit(&b, req.Style, req.Join, idx.PartialMatches())
sc.writeReplaceSplit(b, req.Style, req.Join, idx.PartialMatches())
}
// for _, part := range parts {
// if shouldWriteDelimiter {
Expand Down Expand Up @@ -325,7 +332,9 @@ func isNextTokenNumber(tokens []string, i int) bool {
}

func lowerAndCheck(input string) (string, bool) {
bldr := strings.Builder{}
bldr := builderPool.Get().(*strings.Builder)
bldr.Reset()
defer builderPool.Put(bldr)
bldr.Grow(len(input))
foundLower := false
for _, r := range input {
Expand Down
1 change: 1 addition & 0 deletions converter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func TestConverterConvert(t *testing.T) {

for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
t.Parallel()
params := caps.ConvertRequest{
Style: test.style,
ReplaceStyle: test.repStyle,
Expand Down
4 changes: 2 additions & 2 deletions text/text.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,9 +283,9 @@ func (t Text) IndexFunc(fn func(r rune) bool) int {
// The found result reports whether sep appears in t.
// If sep does not appear in t, cut returns t, "", false.
func (t Text) Cut(sep string) (before, after Text, found bool) {
b, a, f := strings.Cut(t.String(), sep)
tbefore, tafter, f := strings.Cut(t.String(), sep)

return Text(b), Text(a), f
return Text(tbefore), Text(tafter), f
}

// Clone returns a fresh copy of t.
Expand Down
Loading

0 comments on commit 7650644

Please sign in to comment.