Skip to content

Commit 7650644

Browse files
authored
Use sync.Pool in the StdConverter, StdTokenizer, adds text tests, updates build action
* Adds usage of sync.Pool in StdConverter, StdTokenizer * Makes tests run in parallel * Adds text.Test tests * Updates github actions
1 parent d82e985 commit 7650644

File tree

8 files changed

+1495
-23
lines changed

8 files changed

+1495
-23
lines changed

.github/workflows/main.yaml

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,41 @@ jobs:
1111
runs-on: ubuntu-latest
1212
steps:
1313
- uses: actions/checkout@v3
14-
14+
- name: Set up Go
15+
uses: actions/setup-go@v3
16+
with:
17+
go-version: ">=1.19"
18+
check-latest: true
19+
cache: true
20+
- name: Build
21+
run: go build -v ./...
22+
- name: Test
23+
run: go test -v ./...
24+
build-1-18:
25+
runs-on: ubuntu-latest
26+
steps:
27+
- uses: actions/checkout@v3
1528
- name: Set up Go
1629
uses: actions/setup-go@v3
1730
with:
1831
go-version: 1.18
32+
check-latest: true
33+
cache: true
34+
- name: Build
35+
run: go build -v ./...
36+
- name: Test
37+
run: go test -v ./...
38+
build-1-19:
39+
runs-on: ubuntu-latest
40+
steps:
41+
- uses: actions/checkout@v3
1942

43+
- name: Set up Go
44+
uses: actions/setup-go@v3
45+
with:
46+
go-version: 1.19
47+
check-latest: true
48+
cache: true
2049
- name: Build
2150
run: go build -v ./...
2251

README.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
[![Coverage](http://gocover.io/_badge/github.com/chanced/caps)](http://gocover.io/github.com/chanced/caps)
88
![Build Status](https://img.shields.io/github/workflow/status/chanced/caps/Build?style=flat-square)
99

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

321320
- `Text` which has all of the case conversions and relevant functions from strings as methods.
322-
- `Texts` which is a slice of Text
323-
324-
It does not currently have tests.
321+
- `Texts` which is a sortable slice of Text with a few helper methods
325322

326323
```go
327324
package main

caps_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ var titleTestCases = testcases{
6767
func TestToTitle(t *testing.T) {
6868
for _, test := range titleTestCases {
6969
t.Run(test.input, func(t *testing.T) {
70+
t.Parallel()
7071
output := caps.ToTitle(test.input, test.opts...)
7172
if output != test.expected {
7273
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
@@ -76,12 +77,14 @@ func TestToTitle(t *testing.T) {
7677

7778
for _, test := range titleTestCases {
7879
t.Run(test.input, func(t *testing.T) {
80+
t.Parallel()
7981
output := caps.ToTitle(test.input, test.opts...)
8082
if output != test.expected {
8183
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
8284
}
8385
})
8486
t.Run("Caps::"+test.input, func(t *testing.T) {
87+
t.Parallel()
8588
c := caps.New(test.opts.toCapsOpts())
8689
output := c.ToTitle(test.input)
8790
if output != test.expected {
@@ -110,12 +113,14 @@ var camelTestCases = testcases{
110113
func TestToCamel(t *testing.T) {
111114
for _, test := range camelTestCases {
112115
t.Run(test.input, func(t *testing.T) {
116+
t.Parallel()
113117
output := caps.ToCamel(test.input, test.opts...)
114118
if output != test.expected {
115119
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
116120
}
117121
})
118122
t.Run("Caps::"+test.input, func(t *testing.T) {
123+
t.Parallel()
119124
c := caps.New(test.opts.toCapsOpts())
120125
output := c.ToCamel(test.input)
121126
if output != test.expected {
@@ -145,12 +150,14 @@ var lowerCamelTestCases = testcases{
145150
func TestToLowerCamel(t *testing.T) {
146151
for _, test := range lowerCamelTestCases {
147152
t.Run(test.input, func(t *testing.T) {
153+
t.Parallel()
148154
output := caps.ToLowerCamel(test.input, test.opts...)
149155
if output != test.expected {
150156
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
151157
}
152158
})
153159
t.Run("Caps::"+test.input, func(t *testing.T) {
160+
t.Parallel()
154161
c := caps.New(test.opts.toCapsOpts())
155162
output := c.ToLowerCamel(test.input)
156163
if output != test.expected {
@@ -179,12 +186,14 @@ var kebabTestCases = testcases{
179186
func TestToKebab(t *testing.T) {
180187
for _, test := range kebabTestCases {
181188
t.Run(test.input, func(t *testing.T) {
189+
t.Parallel()
182190
output := caps.ToKebab(test.input, test.opts...)
183191
if output != test.expected {
184192
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
185193
}
186194
})
187195
t.Run("Caps::"+test.input, func(t *testing.T) {
196+
t.Parallel()
188197
c := caps.New(test.opts.toCapsOpts())
189198
output := c.ToKebab(test.input)
190199
if output != test.expected {
@@ -213,12 +222,14 @@ var screamingKebabTestCases = testcases{
213222
func TestToScreamingKebab(t *testing.T) {
214223
for _, test := range screamingKebabTestCases {
215224
t.Run(test.input, func(t *testing.T) {
225+
t.Parallel()
216226
output := caps.ToScreamingKebab(test.input, test.opts...)
217227
if output != test.expected {
218228
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
219229
}
220230
})
221231
t.Run("Caps::"+test.input, func(t *testing.T) {
232+
t.Parallel()
222233
c := caps.New(test.opts.toCapsOpts())
223234
output := c.ToScreamingKebab(test.input)
224235
if output != test.expected {
@@ -247,12 +258,14 @@ var snakeTestCases = testcases{
247258
func TestToSnake(t *testing.T) {
248259
for _, test := range snakeTestCases {
249260
t.Run(test.input, func(t *testing.T) {
261+
t.Parallel()
250262
output := caps.ToSnake(test.input, test.opts...)
251263
if output != test.expected {
252264
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
253265
}
254266
})
255267
t.Run("Caps::"+test.input, func(t *testing.T) {
268+
t.Parallel()
256269
c := caps.New(test.opts.toCapsOpts())
257270
output := c.ToSnake(test.input)
258271
if output != test.expected {
@@ -281,12 +294,14 @@ var screamingSnakeTestCases = testcases{
281294
func TestToScreamingSnake(t *testing.T) {
282295
for _, test := range screamingSnakeTestCases {
283296
t.Run(test.input, func(t *testing.T) {
297+
t.Parallel()
284298
output := caps.ToScreamingSnake(test.input, test.opts...)
285299
if output != test.expected {
286300
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
287301
}
288302
})
289303
t.Run("Caps::"+test.input, func(t *testing.T) {
304+
t.Parallel()
290305
c := caps.New(test.opts.toCapsOpts())
291306
output := c.ToScreamingSnake(test.input)
292307
if output != test.expected {
@@ -315,12 +330,14 @@ var dotNotationTestCases = testcases{
315330
func TestToDotNotation(t *testing.T) {
316331
for _, test := range dotNotationTestCases {
317332
t.Run(test.input, func(t *testing.T) {
333+
t.Parallel()
318334
output := caps.ToDotNotation(test.input, test.opts...)
319335
if output != test.expected {
320336
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
321337
}
322338
})
323339
t.Run("Caps::"+test.input, func(t *testing.T) {
340+
t.Parallel()
324341
c := caps.New(test.opts.toCapsOpts())
325342
output := c.ToDotNotation(test.input)
326343
if output != test.expected {
@@ -349,12 +366,14 @@ var screamingDotNotationTestCases = testcases{
349366
func TestToDelimited(t *testing.T) {
350367
for _, test := range dotNotationTestCases {
351368
t.Run(test.input, func(t *testing.T) {
369+
t.Parallel()
352370
output := caps.ToDelimited(test.input, ".", true, test.opts...)
353371
if output != test.expected {
354372
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
355373
}
356374
})
357375
t.Run("Caps::"+test.input, func(t *testing.T) {
376+
t.Parallel()
358377
c := caps.New(test.opts.toCapsOpts())
359378
output := c.ToDelimited(test.input, ".", true)
360379
if output != test.expected {
@@ -367,12 +386,14 @@ func TestToDelimited(t *testing.T) {
367386
func TestToScreamingDotNotation(t *testing.T) {
368387
for _, test := range screamingDotNotationTestCases {
369388
t.Run(test.input, func(t *testing.T) {
389+
t.Parallel()
370390
output := caps.ToScreamingDotNotation(test.input, test.opts...)
371391
if output != test.expected {
372392
t.Errorf("expected \"%s\", got \"%s\"", test.expected, output)
373393
}
374394
})
375395
t.Run("Caps::"+test.input, func(t *testing.T) {
396+
t.Parallel()
376397
c := caps.New(test.opts.toCapsOpts())
377398
output := c.ToScreamingDotNotation(test.input)
378399
if output != test.expected {
@@ -384,6 +405,7 @@ func TestToScreamingDotNotation(t *testing.T) {
384405

385406
func TestCapsAccessors(t *testing.T) {
386407
t.Run("ReplaceStyle", func(t *testing.T) {
408+
t.Parallel()
387409
c := caps.New()
388410
rs := c.ReplaceStyle()
389411
if rs != caps.ReplaceStyleScreaming {
@@ -400,6 +422,7 @@ func TestCapsAccessors(t *testing.T) {
400422
}
401423
})
402424
t.Run("NumberRules", func(t *testing.T) {
425+
t.Parallel()
403426
c := caps.New()
404427
nr := c.NumberRules()
405428
if nr != nil {
@@ -414,6 +437,7 @@ func TestCapsAccessors(t *testing.T) {
414437
}
415438
})
416439
t.Run("AllowedSymbols", func(t *testing.T) {
440+
t.Parallel()
417441
c := caps.New()
418442
as := c.AllowedSymbols()
419443
if as != "" {

converter.go

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,21 @@ package caps
2626

2727
import (
2828
"strings"
29+
"sync"
2930
"unicode"
3031

3132
"github.com/chanced/caps/index"
3233
"github.com/chanced/caps/token"
3334
)
3435

3536
// DefaultConverter is the default Converter instance.
36-
var DefaultConverter = NewConverter(DefaultReplacements, DefaultTokenizer, token.DefaultCaser)
37+
var DefaultConverter Converter = NewConverter(DefaultReplacements, DefaultTokenizer, token.DefaultCaser)
38+
39+
var builderPool = sync.Pool{
40+
New: func() any {
41+
return new(strings.Builder)
42+
},
43+
}
3744

3845
// Converter is an interface satisfied by types which can convert the case of a
3946
// string.
@@ -121,8 +128,6 @@ func (sc StdConverter) Replacements() []Replacement {
121128
Screaming: v.Screaming,
122129
}
123130
}
124-
b := strings.Builder{}
125-
b.WriteByte('.')
126131
return res
127132
}
128133

@@ -214,10 +219,12 @@ func (sc StdConverter) writeReplaceSplit(b *strings.Builder, style Style, join s
214219
// Convert formats the string with the desired style.
215220
func (sc StdConverter) Convert(req ConvertRequest) string {
216221
tokens := sc.tokenizer.Tokenize(req.Input, req.AllowedSymbols, req.NumberRules)
217-
b := strings.Builder{}
218222
if len(tokens) == 0 {
219223
return ""
220224
}
225+
b := builderPool.Get().(*strings.Builder)
226+
b.Reset()
227+
defer builderPool.Put(b)
221228
if len(req.Join) > 0 {
222229
b.Grow(len(req.Input) + len(req.Join)*(len(tokens)-1))
223230
} else {
@@ -235,54 +242,54 @@ func (sc StdConverter) Convert(req ConvertRequest) string {
235242
if idx.LastMatch().HasValue() {
236243
// appending the last match
237244
// formatIndexedReplacement(req.Style, req.ReplaceStyle, b.Len(), idx.LastMatch()), req.Join
238-
sc.writeIndexReplacement(&b, req.Style, req.ReplaceStyle, req.Join, idx.LastMatch())
245+
sc.writeIndexReplacement(b, req.Style, req.ReplaceStyle, req.Join, idx.LastMatch())
239246
}
240247
if idx.HasPartialMatches() {
241248
// checking to make sure it isn't a number
242249
if token.IsNumber(token.Append(sc.caser, tok, idx.PartialMatches()), req.NumberRules) {
243250
b.WriteString(FormatToken(sc.caser, req.Style, b.Len(), token.Append(sc.caser, tok, idx.PartialMatches())))
244251
addedAsNumber = true
245252
} else {
246-
sc.writeReplaceSplit(&b, req.Style, req.Join, idx.PartialMatches())
253+
sc.writeReplaceSplit(b, req.Style, req.Join, idx.PartialMatches())
247254
addedAsNumber = false
248255
}
249256
}
250257
if !addedAsNumber {
251-
sc.writeToken(&b, req.Style, req.Join, tok)
258+
sc.writeToken(b, req.Style, req.Join, tok)
252259
}
253260
// resetting the index
254261
idx = sc.Index()
255262
}
256263
default:
257264
if idx.HasMatched() {
258-
sc.writeIndexReplacement(&b, req.Style, req.ReplaceStyle, req.Join, idx.LastMatch())
265+
sc.writeIndexReplacement(b, req.Style, req.ReplaceStyle, req.Join, idx.LastMatch())
259266
}
260267
if idx.HasPartialMatches() {
261-
sc.writeReplaceSplit(&b, req.Style, req.Join, idx.PartialMatches())
268+
sc.writeReplaceSplit(b, req.Style, req.Join, idx.PartialMatches())
262269
}
263270
if idx.HasMatched() || idx.HasPartialMatches() {
264271
// resetting index
265272
idx = sc.Index()
266273
}
267274
if rep, ok := idx.Get(tok); ok {
268-
sc.writeIndexReplacement(&b, req.Style, req.ReplaceStyle, req.Join, rep)
275+
sc.writeIndexReplacement(b, req.Style, req.ReplaceStyle, req.Join, rep)
269276
} else if isNextTokenNumber(tokens, i) {
270277
if idx, ok = idx.Match(tok); !ok {
271-
sc.writeToken(&b, req.Style, req.Join, tok)
278+
sc.writeToken(b, req.Style, req.Join, tok)
272279
idx = sc.Index()
273280
}
274281
} else {
275-
sc.writeToken(&b, req.Style, req.Join, tok)
282+
sc.writeToken(b, req.Style, req.Join, tok)
276283
}
277284
}
278285
}
279286
if idx.HasMatched() {
280-
sc.writeIndexReplacement(&b, req.Style, req.ReplaceStyle, req.Join, idx.LastMatch())
287+
sc.writeIndexReplacement(b, req.Style, req.ReplaceStyle, req.Join, idx.LastMatch())
281288
// parts = append(parts, formatIndexedReplacement(req.Style, req.ReplaceStyle, len(parts), idx.LastMatch()))
282289
}
283290

284291
if idx.HasPartialMatches() {
285-
sc.writeReplaceSplit(&b, req.Style, req.Join, idx.PartialMatches())
292+
sc.writeReplaceSplit(b, req.Style, req.Join, idx.PartialMatches())
286293
}
287294
// for _, part := range parts {
288295
// if shouldWriteDelimiter {
@@ -325,7 +332,9 @@ func isNextTokenNumber(tokens []string, i int) bool {
325332
}
326333

327334
func lowerAndCheck(input string) (string, bool) {
328-
bldr := strings.Builder{}
335+
bldr := builderPool.Get().(*strings.Builder)
336+
bldr.Reset()
337+
defer builderPool.Put(bldr)
329338
bldr.Grow(len(input))
330339
foundLower := false
331340
for _, r := range input {

converter_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ func TestConverterConvert(t *testing.T) {
6767

6868
for _, test := range tests {
6969
t.Run(test.input, func(t *testing.T) {
70+
t.Parallel()
7071
params := caps.ConvertRequest{
7172
Style: test.style,
7273
ReplaceStyle: test.repStyle,

text/text.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,9 +283,9 @@ func (t Text) IndexFunc(fn func(r rune) bool) int {
283283
// The found result reports whether sep appears in t.
284284
// If sep does not appear in t, cut returns t, "", false.
285285
func (t Text) Cut(sep string) (before, after Text, found bool) {
286-
b, a, f := strings.Cut(t.String(), sep)
286+
tbefore, tafter, f := strings.Cut(t.String(), sep)
287287

288-
return Text(b), Text(a), f
288+
return Text(tbefore), Text(tafter), f
289289
}
290290

291291
// Clone returns a fresh copy of t.

0 commit comments

Comments
 (0)